From ad181d491c0f94c6e723ca4fadac0630a9650a48 Mon Sep 17 00:00:00 2001 From: Gaspard Petit Date: Mon, 22 Sep 2025 16:24:13 -0400 Subject: [PATCH 1/2] Add release pipeline --- .github/workflows/release.yml | 112 ++++++++++++++++++++++++++++++++++ AGENTS.md | 4 +- CONTRIB.md | 15 +++++ README.md | 10 +-- RELEASE.md | 15 +++++ 5 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 CONTRIB.md create mode 100644 RELEASE.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6f45ca5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,112 @@ +name: Release WordAI Add-in + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + runs-on: windows-latest + env: + BUILD_CONFIGURATION: Release + steps: + - uses: actions/checkout@v4 + + - name: Prepare build metadata + shell: pwsh + run: | + $tag = $env:GITHUB_REF_NAME + if (-not $tag) { throw "GITHUB_REF_NAME is not set." } + if ($tag.StartsWith("v")) { $tag = $tag.Substring(1) } + if ($tag -notmatch '^\d+(\.\d+){2,3}$') { + throw "Tags must follow v." + } + $segments = $tag.Split(".") + if ($segments.Count -eq 3) { + $appVersion = "$tag.0" + } else { + $appVersion = $tag + } + $publishDir = Join-Path $env:RUNNER_TEMP "publish" + if (-not (Test-Path $publishDir)) { + New-Item -ItemType Directory -Path $publishDir | Out-Null + } + if (-not $publishDir.EndsWith("\\")) { + $publishDir += "\\" + } + $packageName = "WordAI-$tag.zip" + $packagePath = Join-Path $env:RUNNER_TEMP $packageName + "RELEASE_VERSION=$tag" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "APPLICATION_VERSION=$appVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "PUBLISH_DIR=$publishDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "RELEASE_PACKAGE=$packagePath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Restore NuGet packages + working-directory: WordAI.VSTO + run: nuget restore WordAI.VSTO.csproj -SolutionDirectory . + + - name: Run unit tests + shell: pwsh + run: | + $env:DOTNET_ROLL_FORWARD = 'Major' + dotnet test WordAI.Tests/WordAI.Tests.csproj + + - name: Create temporary signing certificate + working-directory: WordAI.VSTO + shell: pwsh + run: | + $cert = New-SelfSignedCertificate -Type CodeSigning -Subject "CN=WordAI" -CertStoreLocation Cert:\CurrentUser\My + "CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Publish add-in + working-directory: WordAI.VSTO + shell: pwsh + run: | + $msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" + $publishDir = $env:PUBLISH_DIR + $arguments = @( + "WordAI.VSTO.csproj", + "/target:Publish", + "/p:Configuration=$env:BUILD_CONFIGURATION", + "/p:TargetFrameworkVersion=v4.8", + "/p:PublishDir=$publishDir", + "/p:ApplicationVersion=$env:APPLICATION_VERSION", + "/p:ApplicationRevision=0", + "/p:ManifestCertificateThumbprint=$env:CERT_THUMBPRINT", + "/p:ManifestKeyFile=" + ) + & $msbuild @arguments + + - name: Create release package + shell: pwsh + run: | + $publishDir = $env:PUBLISH_DIR + $packagePath = $env:RELEASE_PACKAGE + if (Test-Path $packagePath) { Remove-Item $packagePath -Force } + Compress-Archive -Path "$publishDir*" -DestinationPath $packagePath -Force + + - name: Upload installer artifact + uses: actions/upload-artifact@v4 + with: + name: WordAI-${{ env.RELEASE_VERSION }} + path: ${{ env.RELEASE_PACKAGE }} + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: WordAI ${{ env.RELEASE_VERSION }} + generate_release_notes: true + files: ${{ env.RELEASE_PACKAGE }} + + - name: Remove temporary certificate + if: always() + shell: pwsh + run: | + if ($env:CERT_THUMBPRINT) { + Remove-Item -Path "Cert:\CurrentUser\My\$($env:CERT_THUMBPRINT)" -ErrorAction SilentlyContinue + } diff --git a/AGENTS.md b/AGENTS.md index 9b6ff5e..8cc9f5a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,4 +3,6 @@ - Use four-space indentation and LF line endings. - Run tests with `DOTNET_ROLL_FORWARD=Major dotnet test WordAI.Tests/WordAI.Tests.csproj` before committing. - The `WordAI.VSTO` project targets .NET Framework 4.8 and requires Visual Studio on Windows; avoid building it on Linux. -- Update this file and the README when build or test instructions change. +- Tag releases as `vX.Y.Z` and push the tag to trigger the GitHub Actions release workflow. +- Update this file, the README, `CONTRIB.md`, and `RELEASE.md` when build or test instructions change. + diff --git a/CONTRIB.md b/CONTRIB.md new file mode 100644 index 0000000..c002372 --- /dev/null +++ b/CONTRIB.md @@ -0,0 +1,15 @@ +# Contributor Guide + +## Local Setup + +- Use four-space indentation and LF line endings. +- Restore NuGet packages with `nuget restore WordAI.VSTO/WordAI.VSTO.csproj -SolutionDirectory WordAI.VSTO` if Visual Studio does not do it automatically. + +## Testing + +- Run the prompt manager unit tests with `DOTNET_ROLL_FORWARD=Major dotnet test WordAI.Tests/WordAI.Tests.csproj`. + +## Building the VSTO Add-in + +- Build on a Windows machine with Visual Studio 2022 or later and the .NET Framework 4.8 tooling installed. +- Use the `Release` configuration to generate the ClickOnce output in `WordAI.VSTO/bin/Release/`. diff --git a/README.md b/README.md index ca0f0b4..d76d164 100644 --- a/README.md +++ b/README.md @@ -82,13 +82,5 @@ The GitHub workflow builds the VSTO add-in using a self-signed certificate gener WordAI is definitely BETA software. I use it and "it works on my machine" - Install at your own risks, and reach out if you would like to collaborate for improvements ! -## Development - -The repository includes unit tests for the prompt management features. If your environment only has a newer .NET SDK installed, you can run the tests with: - -```bash -DOTNET_ROLL_FORWARD=Major dotnet test WordAI.Tests/WordAI.Tests.csproj -``` - -Building the VSTO add-in itself requires Windows, Visual Studio and the .NET Framework 4.8 tooling. +See `CONTRIB.md` for local build and test instructions, and `RELEASE.md` for publishing guidance. diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..20c9799 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,15 @@ +# Release Guide + +## Automated Release Workflow + +- The `Release WordAI Add-in` GitHub Actions workflow runs on tags that match `v*`. +- It restores NuGet packages, runs unit tests, publishes the ClickOnce installer with a temporary self-signed certificate, and zips the output (`setup.exe`, the `.vsto` manifest, and `Application Files/`). +- The zipped installer is attached to both the workflow run as an artifact and to the GitHub release generated for the tag. +- Tag versions in `major.minor.patch` form (for example `v1.2.3`). The workflow pads the ClickOnce `ApplicationVersion` with a `.0` revision segment when necessary. + +## Publishing a Release + +1. Push the commit you want to release to GitHub. +2. Create an annotated tag such as `v1.2.3` that reflects the desired version number. +3. Push the tag (`git push origin v1.2.3`). +4. Monitor the `Release WordAI Add-in` workflow run and confirm that the generated GitHub release includes the packaged installer. From 909b62f6d2b74bb93f37ad075b52a3c6135c005e Mon Sep 17 00:00:00 2001 From: Gaspard Petit Date: Mon, 22 Sep 2025 16:48:38 -0400 Subject: [PATCH 2/2] Use stable certificate for signing --- .github/workflows/release.yml | 26 +++++++++++++++++--------- AGENTS.md | 1 + CONTRIB.md | 3 ++- RELEASE.md | 5 +++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f45ca5..211c879 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,8 +46,7 @@ jobs: "RELEASE_PACKAGE=$packagePath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Restore NuGet packages - working-directory: WordAI.VSTO - run: nuget restore WordAI.VSTO.csproj -SolutionDirectory . + run: nuget restore WordAI.VSTO/WordAI.VSTO.csproj -SolutionDirectory . - name: Run unit tests shell: pwsh @@ -55,12 +54,21 @@ jobs: $env:DOTNET_ROLL_FORWARD = 'Major' dotnet test WordAI.Tests/WordAI.Tests.csproj - - name: Create temporary signing certificate - working-directory: WordAI.VSTO + - name: Import signing certificate shell: pwsh + env: + PFX_BASE64: ${{ secrets.WORDAI_CODE_SIGNING_PFX }} + PFX_PASSWORD: ${{ secrets.WORDAI_CODE_SIGNING_PASSWORD }} run: | - $cert = New-SelfSignedCertificate -Type CodeSigning -Subject "CN=WordAI" -CertStoreLocation Cert:\CurrentUser\My + if (-not $env:PFX_BASE64) { throw "WORDAI_CODE_SIGNING_PFX is not configured." } + if (-not $env:PFX_PASSWORD) { throw "WORDAI_CODE_SIGNING_PASSWORD is not configured." } + $pfxPath = Join-Path $env:RUNNER_TEMP 'wordai-signing.pfx' + [IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:PFX_BASE64)) + $securePassword = ConvertTo-SecureString -String $env:PFX_PASSWORD -AsPlainText -Force + $cert = Import-PfxCertificate -FilePath $pfxPath -CertStoreLocation Cert:\CurrentUser\My -Password $securePassword + if (-not $cert) { throw "Failed to import signing certificate." } "CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + Remove-Item $pfxPath -Force - name: Publish add-in working-directory: WordAI.VSTO @@ -93,17 +101,17 @@ jobs: uses: actions/upload-artifact@v4 with: name: WordAI-${{ env.RELEASE_VERSION }} - path: ${{ env.RELEASE_PACKAGE }} + path: ${{ env:RELEASE_PACKAGE }} - name: Create GitHub release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.ref_name }} - name: WordAI ${{ env.RELEASE_VERSION }} + name: WordAI ${{ env:RELEASE_VERSION }} generate_release_notes: true - files: ${{ env.RELEASE_PACKAGE }} + files: ${{ env:RELEASE_PACKAGE }} - - name: Remove temporary certificate + - name: Remove imported certificate if: always() shell: pwsh run: | diff --git a/AGENTS.md b/AGENTS.md index 8cc9f5a..4b7f2d4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,5 +4,6 @@ - Run tests with `DOTNET_ROLL_FORWARD=Major dotnet test WordAI.Tests/WordAI.Tests.csproj` before committing. - The `WordAI.VSTO` project targets .NET Framework 4.8 and requires Visual Studio on Windows; avoid building it on Linux. - Tag releases as `vX.Y.Z` and push the tag to trigger the GitHub Actions release workflow. +- Keep the signing secrets (`WORDAI_CODE_SIGNING_PFX`, `WORDAI_CODE_SIGNING_PASSWORD`) current in the repository settings. - Update this file, the README, `CONTRIB.md`, and `RELEASE.md` when build or test instructions change. diff --git a/CONTRIB.md b/CONTRIB.md index c002372..ef9e6e0 100644 --- a/CONTRIB.md +++ b/CONTRIB.md @@ -3,7 +3,7 @@ ## Local Setup - Use four-space indentation and LF line endings. -- Restore NuGet packages with `nuget restore WordAI.VSTO/WordAI.VSTO.csproj -SolutionDirectory WordAI.VSTO` if Visual Studio does not do it automatically. +- Restore NuGet packages with `nuget restore WordAI.VSTO/WordAI.VSTO.csproj -SolutionDirectory .` if Visual Studio does not do it automatically. ## Testing @@ -13,3 +13,4 @@ - Build on a Windows machine with Visual Studio 2022 or later and the .NET Framework 4.8 tooling installed. - Use the `Release` configuration to generate the ClickOnce output in `WordAI.VSTO/bin/Release/`. + diff --git a/RELEASE.md b/RELEASE.md index 20c9799..3e316eb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,8 +3,8 @@ ## Automated Release Workflow - The `Release WordAI Add-in` GitHub Actions workflow runs on tags that match `v*`. -- It restores NuGet packages, runs unit tests, publishes the ClickOnce installer with a temporary self-signed certificate, and zips the output (`setup.exe`, the `.vsto` manifest, and `Application Files/`). -- The zipped installer is attached to both the workflow run as an artifact and to the GitHub release generated for the tag. +- It restores NuGet packages, runs unit tests, publishes the ClickOnce installer with a signing certificate, and zips the output (`setup.exe`, the `.vsto` manifest, and `Application Files/`). +- Store the base64-encoded `.pfx` in the `WORDAI_CODE_SIGNING_PFX` secret and its password in `WORDAI_CODE_SIGNING_PASSWORD` so the workflow can import the certificate during the build. - Tag versions in `major.minor.patch` form (for example `v1.2.3`). The workflow pads the ClickOnce `ApplicationVersion` with a `.0` revision segment when necessary. ## Publishing a Release @@ -13,3 +13,4 @@ 2. Create an annotated tag such as `v1.2.3` that reflects the desired version number. 3. Push the tag (`git push origin v1.2.3`). 4. Monitor the `Release WordAI Add-in` workflow run and confirm that the generated GitHub release includes the packaged installer. +