From edb4d8905c7284ff00a476090ed3d5156bd61614 Mon Sep 17 00:00:00 2001 From: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:10:06 +0300 Subject: [PATCH 1/7] ci: support SignPath or trusted PFX signing --- .github/workflows/deploy.yml | 50 +++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index acaf095..614e3b5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -171,20 +171,34 @@ jobs: name: msixbundle-unsigned path: bundle-output/DefinitionForCommandPalette.msixbundle - - name: Preflight SignPath config + - name: Select signing provider + id: signing_provider shell: pwsh env: SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} + SIGNING_CERT_BASE64: ${{ secrets.SIGNING_CERT_BASE64 }} run: | - if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_API_TOKEN)) { throw "Missing secret SIGNPATH_API_TOKEN" } - if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_ORGANIZATION_ID)) { throw "Missing variable SIGNPATH_ORGANIZATION_ID" } - if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_PROJECT_SLUG)) { throw "Missing variable SIGNPATH_PROJECT_SLUG" } - if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_SIGNING_POLICY_SLUG)) { throw "Missing variable SIGNPATH_SIGNING_POLICY_SLUG" } + $hasSignPath = + -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_API_TOKEN) -and + -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_ORGANIZATION_ID) -and + -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_PROJECT_SLUG) -and + -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_SIGNING_POLICY_SLUG) + + if ($hasSignPath) { + "provider=signpath" >> $env:GITHUB_OUTPUT + Write-Host "Signing provider: SignPath Foundation" + } elseif (-not [string]::IsNullOrWhiteSpace($env:SIGNING_CERT_BASE64)) { + "provider=pfx" >> $env:GITHUB_OUTPUT + Write-Host "Signing provider: trusted PFX certificate" + } else { + throw "No trusted signing provider configured. Configure SignPath credentials, or SIGNING_CERT_BASE64 + SIGNING_CERT_PASSWORD with a publicly trusted code-signing certificate." + } - name: Sign bundle via SignPath Foundation + if: steps.signing_provider.outputs.provider == 'signpath' id: signpath_sign uses: signpath/github-action-submit-signing-request@v2 with: @@ -196,7 +210,8 @@ jobs: wait-for-completion: true output-artifact-directory: 'bundle-signed' - - name: Normalize signed bundle path + - name: Normalize SignPath signed bundle + if: steps.signing_provider.outputs.provider == 'signpath' shell: pwsh run: | $signed = Get-ChildItem -Path "bundle-signed" -Recurse -Filter "*.msixbundle" | Select-Object -First 1 @@ -205,7 +220,28 @@ jobs: exit 1 } Copy-Item -Path $signed.FullName -Destination "bundle-output/DefinitionForCommandPalette.msixbundle" -Force - Write-Host "Using signed bundle: $($signed.FullName)" + + - name: Sign bundle with trusted PFX + if: steps.signing_provider.outputs.provider == 'pfx' + shell: pwsh + env: + SIGNING_CERT_BASE64: ${{ secrets.SIGNING_CERT_BASE64 }} + SIGNING_CERT_PASSWORD: ${{ secrets.SIGNING_CERT_PASSWORD }} + run: | + if ([string]::IsNullOrWhiteSpace($env:SIGNING_CERT_PASSWORD)) { + throw "Missing secret SIGNING_CERT_PASSWORD" + } + + $certPath = Join-Path $env:RUNNER_TEMP "codesign.pfx" + [IO.File]::WriteAllBytes($certPath, [Convert]::FromBase64String($env:SIGNING_CERT_BASE64)) + + $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | + Sort-Object { [version]$_.Directory.Parent.Name } -Descending | + Select-Object -First 1 + if (-not $signtool) { throw "signtool.exe not found" } + + & $signtool.FullName sign /fd SHA256 /f $certPath /p $env:SIGNING_CERT_PASSWORD /tr http://timestamp.digicert.com /td SHA256 "bundle-output\DefinitionForCommandPalette.msixbundle" + if ($LASTEXITCODE -ne 0) { throw "signtool failed with exit code $LASTEXITCODE" } - name: Verify signature and extract SignatureSha256 shell: pwsh From 6af40890d29c705af45542fe89755c4980f6d39b Mon Sep 17 00:00:00 2001 From: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:11:11 +0300 Subject: [PATCH 2/7] ci: generate clean current WinGet manifests from signed release --- .github/workflows/winget-publish.yml | 81 +++++++++++++++++++++------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/.github/workflows/winget-publish.yml b/.github/workflows/winget-publish.yml index 36bc418..4c78b80 100644 --- a/.github/workflows/winget-publish.yml +++ b/.github/workflows/winget-publish.yml @@ -59,38 +59,79 @@ jobs: echo "TAG_VERSION=$TAG_VERSION" >> $GITHUB_OUTPUT echo "Resolved tag: $TAG (version $TAG_VERSION)" - - name: Verify release asset exists - shell: bash + - name: Download release bundle + shell: pwsh run: | - TAG="${{ steps.get_release.outputs.TAG }}" - URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/DefinitionForCommandPalette.msixbundle" - STATUS=$(curl -o /dev/null -s -w "%{http_code}" -L "$URL") - if [ "$STATUS" != "200" ] && [ "$STATUS" != "302" ]; then - echo "::error::Release asset not found (HTTP $STATUS): $URL" - exit 1 - fi - echo "✓ MSIX bundle asset verified for $TAG" + $tag = "${{ steps.get_release.outputs.TAG }}" + $url = "https://github.com/${{ github.repository }}/releases/download/$tag/DefinitionForCommandPalette.msixbundle" + Invoke-WebRequest -Uri $url -OutFile DefinitionForCommandPalette.msixbundle + if (-not (Test-Path DefinitionForCommandPalette.msixbundle)) { + throw "Release asset download failed: $url" + } - - name: Package winget manifests + - name: Generate current winget manifests shell: pwsh run: | $tag = "${{ steps.get_release.outputs.TAG }}" $tagVersion = "${{ steps.get_release.outputs.TAG_VERSION }}" + $parts = @($tagVersion.Split('.')) + while ($parts.Count -lt 4) { $parts += '0' } + $packageVersion = ($parts[0..3] -join '.') + $url = "https://github.com/${{ github.repository }}/releases/download/$tag/DefinitionForCommandPalette.msixbundle" + $installerHash = (Get-FileHash DefinitionForCommandPalette.msixbundle -Algorithm SHA256).Hash - if (-not (Test-Path winget)) { - Write-Host "::error::No winget/ directory at repo root — cannot package manifests." - exit 1 + $extractDir = Join-Path $env:RUNNER_TEMP "bundle-signature" + if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force } + [System.IO.Compression.ZipFile]::ExtractToDirectory( + (Resolve-Path DefinitionForCommandPalette.msixbundle), + $extractDir + ) + $sigFile = Join-Path $extractDir "AppxSignature.p7x" + if (-not (Test-Path $sigFile)) { + throw "AppxSignature.p7x not found. Refusing to package an unsigned MSIX bundle for WinGet." } + $signatureHash = (Get-FileHash $sigFile -Algorithm SHA256).Hash - $manifestFiles = Get-ChildItem -Path winget -Recurse -File - if ($manifestFiles.Count -eq 0) { - Write-Host "::error::No manifest files under winget/." - exit 1 + $stage = "winget-current" + New-Item -ItemType Directory -Force -Path $stage | Out-Null + + $files = @( + "ruslanlap.DefinitionForCommandPalette.yaml", + "ruslanlap.DefinitionForCommandPalette.installer.yaml", + "ruslanlap.DefinitionForCommandPalette.locale.en-US.yaml", + "ruslanlap.DefinitionForCommandPalette.locale.fr-FR.yaml" + ) + + foreach ($name in $files) { + $src = Join-Path "winget" $name + if (-not (Test-Path $src)) { throw "Missing template manifest: $src" } + $text = Get-Content $src -Raw + $text = $text -replace '(?m)^PackageVersion:\s*.*$', "PackageVersion: $packageVersion" + $text = $text -replace 'winget-manifest\.([^.]+)\.[0-9.]+\.schema\.json', 'winget-manifest.$1.1.12.0.schema.json' + $text = $text -replace '(?m)^ManifestVersion:\s*.*$', 'ManifestVersion: 1.12.0' + + if ($name -like '*.installer.yaml') { + $text = $text -replace '(?m)^\s*InstallerUrl:\s*.*$', " InstallerUrl: $url" + $text = $text -replace '(?m)^\s*InstallerSha256:\s*.*$', " InstallerSha256: $installerHash" + $text = [regex]::Replace($text, '(?m)^\s*SignatureSha256:\s*.*\r?\n?', '') + $needle = " InstallerSha256: $installerHash" + $replacement = " InstallerSha256: $installerHash`r`n SignatureSha256: $signatureHash" + $text = $text.Replace($needle, $replacement) + if ($text -notmatch '(?m)^MinimumOSVersion:') { + $text = $text -replace '(?m)^InstallerType:\s*msix\s*$', "MinimumOSVersion: 10.0.19041.0`r`nInstallerType: msix" + } + } + + Set-Content -Path (Join-Path $stage $name) -Value $text -NoNewline } + Write-Host "PackageVersion: $packageVersion" + Write-Host "InstallerSha256: $installerHash" + Write-Host "SignatureSha256: $signatureHash" + $zip = "winget-manifests-$tag.zip" - Compress-Archive -Path winget/* -DestinationPath $zip -Force - Write-Host "Packaged $($manifestFiles.Count) file(s) into $zip" + Compress-Archive -Path "$stage/*" -DestinationPath $zip -Force + Write-Host "Packaged exactly 4 current-version manifests into $zip" - name: Upload winget manifests artifact uses: actions/upload-artifact@v4 From 0b551cf79d6c545d5edc74bcac4a3c3ed44a37ab Mon Sep 17 00:00:00 2001 From: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:11:38 +0300 Subject: [PATCH 3/7] docs: document trusted signing and generated WinGet manifests --- winget/SUBMIT.md | 112 ++++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 60 deletions(-) diff --git a/winget/SUBMIT.md b/winget/SUBMIT.md index c981c21..159d491 100644 --- a/winget/SUBMIT.md +++ b/winget/SUBMIT.md @@ -1,64 +1,56 @@ # Submitting to winget-pkgs -This repository ships the winget manifests under `winget/`. The CI workflow -[`.github/workflows/winget-publish.yml`](../.github/workflows/winget-publish.yml) -packages them into an artifact on every Microsoft Store release, but it does -**not** open the upstream PR. Submitting the manifests to -[`microsoft/winget-pkgs`](https://github.com/microsoft/winget-pkgs) is a -manual step that takes about five minutes. - -## Why the workflow doesn't submit - -- `wingetcreate new` requires Sharprompt interactive input. It cannot run - in CI without a TTY and fails with - `Sharprompt requires an interactive environment`. -- `wingetcreate update` requires the package to already exist in - `microsoft/winget-pkgs`. This package does not. -- The winget-pkgs GitHub App is the recommended path, but configuring it is - a separate project. - -## When you need to do this - -Every time you bump `PackageVersion` in `winget/*.yaml`. The CI artifact is -the source of truth — pull it after the Store publish completes. - -## Procedure - -1. Wait for the `Publish CmdPal Extension to Microsoft Store` workflow run - to complete successfully. -2. Download the `winget-manifests-` artifact from the matching - `Publish to WinGet` run. -3. Fork [`microsoft/winget-pkgs`](https://github.com/microsoft/winget-pkgs) - if you have not already. -4. Create a branch named after the package and version: +The repository keeps four manifest templates under `winget/`. The +`.github/workflows/winget-publish.yml` workflow turns those templates into a +clean, current-version manifest set after a signed GitHub release is available. + +## Signing requirement + +The distributed `.msixbundle` must be signed by a certificate chain trusted by +Windows. The release workflow supports either: + +- SignPath Foundation via `SIGNPATH_API_TOKEN` plus the SignPath repository variables. +- A publicly trusted PFX code-signing certificate via `SIGNING_CERT_BASE64` and + `SIGNING_CERT_PASSWORD`. + +A self-signed certificate is not sufficient for winget validation and produces +`0x800B0109 / CERT_E_UNTRUSTEDROOT`. + +## What CI now generates + +For the requested release tag, `Publish to WinGet`: + +1. Downloads `DefinitionForCommandPalette.msixbundle` from the GitHub Release. +2. Computes the bundle `InstallerSha256`. +3. Extracts `AppxSignature.p7x` and computes `SignatureSha256`. +4. Normalizes the release tag to a four-part `PackageVersion`. +5. Updates the manifest schema/version to 1.12.0. +6. Packages exactly these four current-version files: + - `ruslanlap.DefinitionForCommandPalette.yaml` + - `ruslanlap.DefinitionForCommandPalette.installer.yaml` + - `ruslanlap.DefinitionForCommandPalette.locale.en-US.yaml` + - `ruslanlap.DefinitionForCommandPalette.locale.fr-FR.yaml` + +Historical folders under `winget/` are intentionally excluded from the +artifact so a submission cannot accidentally contain multiple package versions. + +## Submission procedure + +1. Ensure the release workflow completed and the GitHub Release contains the + trusted-signed `DefinitionForCommandPalette.msixbundle`. +2. Run `Publish to WinGet` for that release tag. +3. Download the `winget-manifests-` artifact. +4. Copy the four files into: + `manifests/r/ruslanlap/DefinitionForCommandPalette//` + in the `microsoft/winget-pkgs` fork. +5. Validate with: + ```powershell + winget validate --manifest ``` - git checkout -b ruslanlap/DefinitionForCommandPalette-1.0.4 - ``` -5. Copy the manifests into the canonical path: - ``` - cp winget-manifests-1.0.4.zip /tmp/ - cd /tmp && unzip winget-manifests-1.0.4.zip -d manifests - mkdir -p upstream/manifests/r/ruslanlap/DefinitionForCommandPalette/1.0.4.0 - cp -r manifests/* upstream/manifests/r/ruslanlap/DefinitionForCommandPalette/1.0.4.0/ - ``` -6. Update `PackageVersion`, `InstallerUrl`, and `InstallerSha256` in the - manifests to match the new release. The current values point at v1.0.0 - and need to be bumped before each submission. -7. Validate locally with `winget validate --manifest `. -8. Commit, push, open a PR upstream. -9. Watch the validation checks. Common expected labels for this package - type (MSIX bundle with no primary executable): - - `Validation-Executable-Error` - - `Validation-No-Executables` - - These are routine for MSIX-only packages. Comment on the PR explaining - that the install path is `Plugins/DefinitionForCommandPalette` under the - PowerToys Run installation directory, and link the relevant section of - `deploy.yml` if the moderator asks. -10. Wait for a moderator to merge. Do not ping more than twice. - -## Why hand-write instead of using wingetcreate +6. Commit, push, and open/update the upstream PR. +7. Wait for the winget validation pipeline and reviewer approval. -Hand-written manifests are easier to review, easier to bump across -versions, and easier to keep in sync with the rest of this repo's release -process. The win is small for new packages but compounds across versions. \ No newline at end of file +For the current v1.0.4 submission, upstream PR #431811 has already confirmed +that the only remaining validation blocker is the untrusted certificate chain. +Once a trusted-signed bundle replaces the release asset, regenerate the +manifests and update that PR with the new hashes. From 065d60161a7142c523b6c07f958d131746469bd2 Mon Sep 17 00:00:00 2001 From: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Date: Sat, 26 Sep 2026 00:01:33 +0300 Subject: [PATCH 4/7] ci: run MSIX build validation on pull requests --- .github/workflows/build-msix.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build-msix.yml b/.github/workflows/build-msix.yml index 6f5b570..d5866b0 100644 --- a/.github/workflows/build-msix.yml +++ b/.github/workflows/build-msix.yml @@ -2,6 +2,15 @@ name: Build CmdPal MSIX Bundle on: workflow_dispatch: + pull_request: + branches: [master] + paths: + - 'DefinitionExtension/**' + - 'CmdPal-Definition.sln' + - '.github/workflows/build-msix.yml' + - '.github/workflows/deploy.yml' + - '.github/workflows/winget-publish.yml' + - 'winget/**' permissions: contents: read From 5b47e93714489362fd0d2455299cf409414c700c Mon Sep 17 00:00:00 2001 From: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Date: Sat, 26 Sep 2026 00:03:18 +0300 Subject: [PATCH 5/7] ci: keep SignPath-only release signing until certificate identity is known --- .github/workflows/deploy.yml | 50 +++++------------------------------- 1 file changed, 7 insertions(+), 43 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 614e3b5..acaf095 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -171,34 +171,20 @@ jobs: name: msixbundle-unsigned path: bundle-output/DefinitionForCommandPalette.msixbundle - - name: Select signing provider - id: signing_provider + - name: Preflight SignPath config shell: pwsh env: SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} - SIGNING_CERT_BASE64: ${{ secrets.SIGNING_CERT_BASE64 }} run: | - $hasSignPath = - -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_API_TOKEN) -and - -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_ORGANIZATION_ID) -and - -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_PROJECT_SLUG) -and - -not [string]::IsNullOrWhiteSpace($env:SIGNPATH_SIGNING_POLICY_SLUG) - - if ($hasSignPath) { - "provider=signpath" >> $env:GITHUB_OUTPUT - Write-Host "Signing provider: SignPath Foundation" - } elseif (-not [string]::IsNullOrWhiteSpace($env:SIGNING_CERT_BASE64)) { - "provider=pfx" >> $env:GITHUB_OUTPUT - Write-Host "Signing provider: trusted PFX certificate" - } else { - throw "No trusted signing provider configured. Configure SignPath credentials, or SIGNING_CERT_BASE64 + SIGNING_CERT_PASSWORD with a publicly trusted code-signing certificate." - } + if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_API_TOKEN)) { throw "Missing secret SIGNPATH_API_TOKEN" } + if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_ORGANIZATION_ID)) { throw "Missing variable SIGNPATH_ORGANIZATION_ID" } + if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_PROJECT_SLUG)) { throw "Missing variable SIGNPATH_PROJECT_SLUG" } + if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_SIGNING_POLICY_SLUG)) { throw "Missing variable SIGNPATH_SIGNING_POLICY_SLUG" } - name: Sign bundle via SignPath Foundation - if: steps.signing_provider.outputs.provider == 'signpath' id: signpath_sign uses: signpath/github-action-submit-signing-request@v2 with: @@ -210,8 +196,7 @@ jobs: wait-for-completion: true output-artifact-directory: 'bundle-signed' - - name: Normalize SignPath signed bundle - if: steps.signing_provider.outputs.provider == 'signpath' + - name: Normalize signed bundle path shell: pwsh run: | $signed = Get-ChildItem -Path "bundle-signed" -Recurse -Filter "*.msixbundle" | Select-Object -First 1 @@ -220,28 +205,7 @@ jobs: exit 1 } Copy-Item -Path $signed.FullName -Destination "bundle-output/DefinitionForCommandPalette.msixbundle" -Force - - - name: Sign bundle with trusted PFX - if: steps.signing_provider.outputs.provider == 'pfx' - shell: pwsh - env: - SIGNING_CERT_BASE64: ${{ secrets.SIGNING_CERT_BASE64 }} - SIGNING_CERT_PASSWORD: ${{ secrets.SIGNING_CERT_PASSWORD }} - run: | - if ([string]::IsNullOrWhiteSpace($env:SIGNING_CERT_PASSWORD)) { - throw "Missing secret SIGNING_CERT_PASSWORD" - } - - $certPath = Join-Path $env:RUNNER_TEMP "codesign.pfx" - [IO.File]::WriteAllBytes($certPath, [Convert]::FromBase64String($env:SIGNING_CERT_BASE64)) - - $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | - Sort-Object { [version]$_.Directory.Parent.Name } -Descending | - Select-Object -First 1 - if (-not $signtool) { throw "signtool.exe not found" } - - & $signtool.FullName sign /fd SHA256 /f $certPath /p $env:SIGNING_CERT_PASSWORD /tr http://timestamp.digicert.com /td SHA256 "bundle-output\DefinitionForCommandPalette.msixbundle" - if ($LASTEXITCODE -ne 0) { throw "signtool failed with exit code $LASTEXITCODE" } + Write-Host "Using signed bundle: $($signed.FullName)" - name: Verify signature and extract SignatureSha256 shell: pwsh From 4ff7623bdcda1042967dcddf0d283a8fdb51048f Mon Sep 17 00:00:00 2001 From: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Date: Sat, 26 Sep 2026 00:03:29 +0300 Subject: [PATCH 6/7] ci: harden WinGet release trigger and tag handling --- .github/workflows/winget-publish.yml | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/winget-publish.yml b/.github/workflows/winget-publish.yml index 4c78b80..775d6fd 100644 --- a/.github/workflows/winget-publish.yml +++ b/.github/workflows/winget-publish.yml @@ -37,7 +37,9 @@ jobs: runs-on: windows-latest if: > github.event_name == 'workflow_dispatch' || - github.event.workflow_run.event == 'release' + (github.event_name == 'workflow_run' && + github.event.workflow_run.event == 'release' && + github.event.workflow_run.conclusion == 'success') steps: - name: Checkout code @@ -48,21 +50,30 @@ jobs: shell: bash env: GH_TOKEN: ${{ github.token }} + MANUAL_RELEASE_TAG: ${{ github.event.inputs.release_tag }} run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - TAG="${{ github.event.inputs.release_tag }}" + TAG="$MANUAL_RELEASE_TAG" else TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq '.tag_name') fi + + if [[ ! "$TAG" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then + echo "::error::Invalid release tag: $TAG" + exit 1 + fi + TAG_VERSION="${TAG#v}" - echo "TAG=$TAG" >> $GITHUB_OUTPUT - echo "TAG_VERSION=$TAG_VERSION" >> $GITHUB_OUTPUT + echo "TAG=$TAG" >> "$GITHUB_OUTPUT" + echo "TAG_VERSION=$TAG_VERSION" >> "$GITHUB_OUTPUT" echo "Resolved tag: $TAG (version $TAG_VERSION)" - name: Download release bundle shell: pwsh + env: + RELEASE_TAG: ${{ steps.get_release.outputs.TAG }} run: | - $tag = "${{ steps.get_release.outputs.TAG }}" + $tag = $env:RELEASE_TAG $url = "https://github.com/${{ github.repository }}/releases/download/$tag/DefinitionForCommandPalette.msixbundle" Invoke-WebRequest -Uri $url -OutFile DefinitionForCommandPalette.msixbundle if (-not (Test-Path DefinitionForCommandPalette.msixbundle)) { @@ -71,9 +82,12 @@ jobs: - name: Generate current winget manifests shell: pwsh + env: + RELEASE_TAG: ${{ steps.get_release.outputs.TAG }} + RELEASE_TAG_VERSION: ${{ steps.get_release.outputs.TAG_VERSION }} run: | - $tag = "${{ steps.get_release.outputs.TAG }}" - $tagVersion = "${{ steps.get_release.outputs.TAG_VERSION }}" + $tag = $env:RELEASE_TAG + $tagVersion = $env:RELEASE_TAG_VERSION $parts = @($tagVersion.Split('.')) while ($parts.Count -lt 4) { $parts += '0' } $packageVersion = ($parts[0..3] -join '.') From c3731cf347120266e81d71cb505978b47ee53b97 Mon Sep 17 00:00:00 2001 From: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Date: Sat, 26 Sep 2026 00:04:10 +0300 Subject: [PATCH 7/7] docs: align signing guidance with SignPath-only flow --- winget/SUBMIT.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/winget/SUBMIT.md b/winget/SUBMIT.md index 159d491..03267b9 100644 --- a/winget/SUBMIT.md +++ b/winget/SUBMIT.md @@ -7,14 +7,13 @@ clean, current-version manifest set after a signed GitHub release is available. ## Signing requirement The distributed `.msixbundle` must be signed by a certificate chain trusted by -Windows. The release workflow supports either: - -- SignPath Foundation via `SIGNPATH_API_TOKEN` plus the SignPath repository variables. -- A publicly trusted PFX code-signing certificate via `SIGNING_CERT_BASE64` and - `SIGNING_CERT_PASSWORD`. +Windows. The current release workflow uses SignPath Foundation via `SIGNPATH_API_TOKEN` +plus the SignPath repository variables. A self-signed certificate is not sufficient for winget validation and produces -`0x800B0109 / CERT_E_UNTRUSTEDROOT`. +`0x800B0109 / CERT_E_UNTRUSTEDROOT`. For MSIX, the manifest `Publisher` +must also match the subject of the code-signing certificate, so do not add a +generic PFX fallback without handling package identity explicitly. ## What CI now generates