From 93116a115647acecdf6415017ffc8c9ffb66fd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Andr=C3=A9?= Date: Fri, 12 Jun 2026 20:28:51 +0200 Subject: [PATCH 1/2] fix: 088 wire winget automation into release pipeline Run the Winget updater from the reusable stable release workflow instead of waiting for a separate release.published event. This matches the current release pipeline, where GitHub Actions creates the release with GITHUB_TOKEN and does not fan out a second workflow run. Keep the existing asset polling and Winget submission logic in place, but add release-metadata resolution and a manual workflow_dispatch fallback so maintainers can rerun the job for an existing stable tag when needed. Update the maintainer guide and attach an evidence packet with YAML validation, live release metadata checks, a live asset-resolution run against upstream v0.17.0, and the current nomadworks_validate tool failure for traceability. --- .github/workflows/reusable-release.yml | 12 +++ .github/workflows/update-winget.yml | 80 +++++++++++++++++-- docs/guides/winget-release-automation.md | 11 ++- .../SUMMARY.md | 13 +++ .../logs/changed-files.txt | 11 +++ .../logs/validation.txt | 45 +++++++++++ .../screenshots/README.md | 3 + 7 files changed, 164 insertions(+), 11 deletions(-) create mode 100644 evidences/088-fix-winget-automation-release-trigger/SUMMARY.md create mode 100644 evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt create mode 100644 evidences/088-fix-winget-automation-release-trigger/logs/validation.txt create mode 100644 evidences/088-fix-winget-automation-release-trigger/screenshots/README.md diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index e839e76b4..59b1b0583 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -97,6 +97,18 @@ jobs: release_name: ${{ needs.prepare-release.outputs.release_name }} secrets: inherit + update-winget: + needs: + - prepare-release + - build-and-upload + if: ${{ !inputs.prerelease }} + permissions: + contents: read + uses: ./.github/workflows/update-winget.yml + with: + release_tag: ${{ needs.prepare-release.outputs.tag }} + secrets: inherit + release-ui: needs: prepare-release if: ${{ inputs.release_ui }} diff --git a/.github/workflows/update-winget.yml b/.github/workflows/update-winget.yml index 41b894f67..fd9a656f3 100644 --- a/.github/workflows/update-winget.yml +++ b/.github/workflows/update-winget.yml @@ -1,17 +1,83 @@ name: Update Winget on: - release: - types: - - published + workflow_call: + inputs: + release_tag: + description: "Stable release tag to inspect" + required: true + type: string + release_id: + description: "Optional numeric GitHub release id" + required: false + default: "" + type: string + workflow_dispatch: + inputs: + release_tag: + description: "Stable release tag to inspect" + required: true + type: string + release_id: + description: "Optional numeric GitHub release id" + required: false + default: "" + type: string permissions: contents: read jobs: + resolve-release: + name: Resolve release metadata + runs-on: ubuntu-latest + outputs: + release_id: ${{ steps.release.outputs.release_id }} + release_tag: ${{ steps.release.outputs.release_tag }} + draft: ${{ steps.release.outputs.draft }} + prerelease: ${{ steps.release.outputs.prerelease }} + steps: + - name: Resolve release metadata + id: release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ inputs.release_tag }} + RELEASE_ID_INPUT: ${{ inputs.release_id }} + run: | + set -euo pipefail + + if [ -n "$RELEASE_ID_INPUT" ]; then + release_api="repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID_INPUT}" + else + release_api="repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}" + fi + + release_id="$(gh api "$release_api" --jq '.id')" + release_tag="$(gh api "$release_api" --jq '.tag_name')" + draft="$(gh api "$release_api" --jq '.draft')" + prerelease="$(gh api "$release_api" --jq '.prerelease')" + + if [ -z "$release_id" ] || [ "$release_id" = "null" ]; then + echo "Unable to resolve release id for tag '$RELEASE_TAG'" >&2 + exit 1 + fi + + echo "release_id=$release_id" >> "$GITHUB_OUTPUT" + echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT" + echo "draft=$draft" >> "$GITHUB_OUTPUT" + echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT" + + - name: Log resolved release metadata + run: | + echo "Release tag: ${{ steps.release.outputs.release_tag }}" + echo "Release id: ${{ steps.release.outputs.release_id }}" + echo "Draft: ${{ steps.release.outputs.draft }}" + echo "Prerelease: ${{ steps.release.outputs.prerelease }}" + update-winget: name: Submit Winget manifest update - if: ${{ !github.event.release.draft && !github.event.release.prerelease }} + needs: resolve-release + if: ${{ needs.resolve-release.outputs.draft != 'true' && needs.resolve-release.outputs.prerelease != 'true' }} runs-on: ubuntu-latest env: WINGET_PACKAGE_IDENTIFIER: ${{ vars.WINGET_PACKAGE_IDENTIFIER || 'NeuralNomadsAI.CodeNomad' }} @@ -35,8 +101,8 @@ jobs: run: | args=( --repo "${{ github.repository }}" - --release-id "${{ github.event.release.id }}" - --tag "${{ github.event.release.tag_name }}" + --release-id "${{ needs.resolve-release.outputs.release_id }}" + --tag "${{ needs.resolve-release.outputs.release_tag }}" --asset-name-template "$WINGET_WINDOWS_ASSET_NAME_TEMPLATE" --timeout-seconds "$WINGET_ASSET_WAIT_TIMEOUT_SECONDS" --poll-interval-seconds "$WINGET_ASSET_POLL_INTERVAL_SECONDS" @@ -79,7 +145,7 @@ jobs: with: identifier: ${{ env.WINGET_PACKAGE_IDENTIFIER }} version: ${{ steps.release_asset.outputs.version }} - release-tag: ${{ github.event.release.tag_name }} + release-tag: ${{ needs.resolve-release.outputs.release_tag }} installers-regex: ${{ steps.release_asset.outputs.asset_regex }} fork-user: ${{ env.WINGET_FORK_OWNER }} token: ${{ secrets.WINGET_GITHUB_TOKEN }} diff --git a/docs/guides/winget-release-automation.md b/docs/guides/winget-release-automation.md index 63931a155..b61639c03 100644 --- a/docs/guides/winget-release-automation.md +++ b/docs/guides/winget-release-automation.md @@ -1,11 +1,13 @@ # Winget release automation -CodeNomad publishes Winget updates from GitHub Releases with `.github/workflows/update-winget.yml`. +CodeNomad publishes Winget updates from the stable GitHub release pipeline. `.github/workflows/reusable-release.yml` now calls `.github/workflows/update-winget.yml` after the release assets finish uploading. ## Trigger -- Runs on `release.published`. -- Exits early for draft or prerelease releases. +- Runs as a reusable workflow from the stable release pipeline, after `build-and-upload` completes. +- Resolves the target release by tag through the GitHub API, then exits early for draft or prerelease releases. +- Can also be rerun manually with `workflow_dispatch` by supplying the stable release tag (and optionally the numeric release id). +- This avoids the old `release.published` trap where a release created by GitHub Actions with the default `GITHUB_TOKEN` does not fan out into a second workflow run. - Waits for the expected stable Windows Tauri asset because the release record can exist before all assets finish uploading. ## Required configuration @@ -28,7 +30,7 @@ CodeNomad publishes Winget updates from GitHub Releases with `.github/workflows/ ## Runtime flow -1. Resolve the release version from `github.event.release.tag_name`. +1. Resolve the target release by tag through the GitHub API, then derive the package version from the resolved release tag. 2. Poll the release API until exactly one uploaded asset matches the configured Windows Tauri asset template. 3. Download the matched asset once and compute a SHA-256 for logging and verification. 4. Verify the PAT owner matches `WINGET_FORK_OWNER` and that `${WINGET_FORK_OWNER}/winget-pkgs` is a fork of `microsoft/winget-pkgs`. @@ -38,3 +40,4 @@ CodeNomad publishes Winget updates from GitHub Releases with `.github/workflows/ - The workflow does not depend on a persistent local `winget-pkgs` clone. - The current package in `winget-pkgs` uses the release ZIP with a nested NSIS installer, so the workflow targets the stable `CodeNomad-Tauri-windows-x64-{version}.zip` asset. +- If a maintainer publishes a release outside the standard release workflow, they should manually run `Update Winget` for that stable tag. diff --git a/evidences/088-fix-winget-automation-release-trigger/SUMMARY.md b/evidences/088-fix-winget-automation-release-trigger/SUMMARY.md new file mode 100644 index 000000000..ae6388bef --- /dev/null +++ b/evidences/088-fix-winget-automation-release-trigger/SUMMARY.md @@ -0,0 +1,13 @@ +# Task 088 Evidence Summary + +- Changed workflow files: `.github/workflows/reusable-release.yml` and `.github/workflows/update-winget.yml`. +- Changed maintainer doc: `docs/guides/winget-release-automation.md`. +- AC-1 / AC-2: stable releases now invoke Winget from `.github/workflows/reusable-release.yml` after `build-and-upload`, instead of depending on a separate `release.published` event that GitHub Actions suppresses when the release is created with `GITHUB_TOKEN`. +- AC-3: the existing `scripts/winget/resolve-release-asset.cjs` polling and hash-resolution path plus the existing `vedantmgoyal9/winget-releaser@v2` submission step were preserved. +- AC-4: the maintainer guide now documents the pipeline-coupled trigger model and the manual `workflow_dispatch` fallback for out-of-band stable releases. +- AC-5: validation covered YAML parsing, live release metadata lookups for both stable and prerelease releases, a live run of the release-asset resolver against upstream `v0.17.0`, and a `nomadworks_validate` attempt that currently fails because the validator itself crashes. + +## Limitations + +- No live GitHub Actions run was triggered from this environment, so end-to-end execution inside GitHub remains unverified here. +- `nomadworks_validate` is currently broken in this environment and returned `undefined is not an object (evaluating 'res.warnings.length')`. diff --git a/evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt b/evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt new file mode 100644 index 000000000..523175dfd --- /dev/null +++ b/evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt @@ -0,0 +1,11 @@ +Changed files for task 088 + +git diff --name-only output: +.github/workflows/reusable-release.yml +.github/workflows/update-winget.yml +docs/guides/winget-release-automation.md + +git status --short output before evidence creation: + M .github/workflows/reusable-release.yml + M .github/workflows/update-winget.yml + M docs/guides/winget-release-automation.md diff --git a/evidences/088-fix-winget-automation-release-trigger/logs/validation.txt b/evidences/088-fix-winget-automation-release-trigger/logs/validation.txt new file mode 100644 index 000000000..954ed92eb --- /dev/null +++ b/evidences/088-fix-winget-automation-release-trigger/logs/validation.txt @@ -0,0 +1,45 @@ +Validation commands for task 088 + +1) YAML parse +Command: +python -c "import yaml,sys; [yaml.safe_load(open(path, 'r', encoding='utf-8')) for path in sys.argv[1:]]; print('YAML OK')" ".github/workflows/update-winget.yml" ".github/workflows/reusable-release.yml" + +Output: +YAML OK + +2) Stable release metadata lookup +Command: +gh api "repos/NeuralNomadsAI/CodeNomad/releases/tags/v0.17.0" --jq '"id=\(.id) tag=\(.tag_name) draft=\(.draft) prerelease=\(.prerelease)"' + +Output: +id=336185327 tag=v0.17.0 draft=false prerelease=false + +3) Prerelease metadata lookup +Command: +gh api "repos/NeuralNomadsAI/CodeNomad/releases/tags/v0.17.1-dev-20260609-4a1d53bf" --jq '"id=\(.id) tag=\(.tag_name) draft=\(.draft) prerelease=\(.prerelease)"' + +Output: +id=336372359 tag=v0.17.1-dev-20260609-4a1d53bf draft=false prerelease=true + +4) Live asset resolver run +Command: +$releaseId = gh api "repos/NeuralNomadsAI/CodeNomad/releases/tags/v0.17.0" --jq ".id"; node scripts/winget/resolve-release-asset.cjs --repo "NeuralNomadsAI/CodeNomad" --release-id "$releaseId" --tag "v0.17.0" --timeout-seconds 5 --poll-interval-seconds 1 --json + +Output: +Resolved release id: 336185327 +{ + "asset_name": "CodeNomad-Tauri-windows-x64-0.17.0.zip", + "asset_regex": "^CodeNomad-Tauri-windows-x64-0\\.17\\.0\\.zip$", + "asset_sha256": "4E030C61728447E9D988C6C3F99745D30716B024588F0C626BF44A4607AD1071", + "asset_url": "https://github.com/NeuralNomadsAI/CodeNomad/releases/download/v0.17.0/CodeNomad-Tauri-windows-x64-0.17.0.zip", + "release_url": "https://github.com/NeuralNomadsAI/CodeNomad/releases/tag/v0.17.0", + "tag_name": "v0.17.0", + "version": "0.17.0" +} + +5) nomadworks_validate +Command: +nomadworks_validate + +Output: +undefined is not an object (evaluating 'res.warnings.length') diff --git a/evidences/088-fix-winget-automation-release-trigger/screenshots/README.md b/evidences/088-fix-winget-automation-release-trigger/screenshots/README.md new file mode 100644 index 000000000..d0474e28e --- /dev/null +++ b/evidences/088-fix-winget-automation-release-trigger/screenshots/README.md @@ -0,0 +1,3 @@ +# Screenshots + +No UI changed in task 088, so screenshots are not applicable. From ef129fd50bcc7d87e1cdc1ae60d986e1765e1529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Andr=C3=A9?= Date: Sat, 13 Jun 2026 02:20:41 +0200 Subject: [PATCH 2/2] chore: remove PR 551 evidence artifacts --- .../SUMMARY.md | 13 ------ .../logs/changed-files.txt | 11 ----- .../logs/validation.txt | 45 ------------------- .../screenshots/README.md | 3 -- 4 files changed, 72 deletions(-) delete mode 100644 evidences/088-fix-winget-automation-release-trigger/SUMMARY.md delete mode 100644 evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt delete mode 100644 evidences/088-fix-winget-automation-release-trigger/logs/validation.txt delete mode 100644 evidences/088-fix-winget-automation-release-trigger/screenshots/README.md diff --git a/evidences/088-fix-winget-automation-release-trigger/SUMMARY.md b/evidences/088-fix-winget-automation-release-trigger/SUMMARY.md deleted file mode 100644 index ae6388bef..000000000 --- a/evidences/088-fix-winget-automation-release-trigger/SUMMARY.md +++ /dev/null @@ -1,13 +0,0 @@ -# Task 088 Evidence Summary - -- Changed workflow files: `.github/workflows/reusable-release.yml` and `.github/workflows/update-winget.yml`. -- Changed maintainer doc: `docs/guides/winget-release-automation.md`. -- AC-1 / AC-2: stable releases now invoke Winget from `.github/workflows/reusable-release.yml` after `build-and-upload`, instead of depending on a separate `release.published` event that GitHub Actions suppresses when the release is created with `GITHUB_TOKEN`. -- AC-3: the existing `scripts/winget/resolve-release-asset.cjs` polling and hash-resolution path plus the existing `vedantmgoyal9/winget-releaser@v2` submission step were preserved. -- AC-4: the maintainer guide now documents the pipeline-coupled trigger model and the manual `workflow_dispatch` fallback for out-of-band stable releases. -- AC-5: validation covered YAML parsing, live release metadata lookups for both stable and prerelease releases, a live run of the release-asset resolver against upstream `v0.17.0`, and a `nomadworks_validate` attempt that currently fails because the validator itself crashes. - -## Limitations - -- No live GitHub Actions run was triggered from this environment, so end-to-end execution inside GitHub remains unverified here. -- `nomadworks_validate` is currently broken in this environment and returned `undefined is not an object (evaluating 'res.warnings.length')`. diff --git a/evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt b/evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt deleted file mode 100644 index 523175dfd..000000000 --- a/evidences/088-fix-winget-automation-release-trigger/logs/changed-files.txt +++ /dev/null @@ -1,11 +0,0 @@ -Changed files for task 088 - -git diff --name-only output: -.github/workflows/reusable-release.yml -.github/workflows/update-winget.yml -docs/guides/winget-release-automation.md - -git status --short output before evidence creation: - M .github/workflows/reusable-release.yml - M .github/workflows/update-winget.yml - M docs/guides/winget-release-automation.md diff --git a/evidences/088-fix-winget-automation-release-trigger/logs/validation.txt b/evidences/088-fix-winget-automation-release-trigger/logs/validation.txt deleted file mode 100644 index 954ed92eb..000000000 --- a/evidences/088-fix-winget-automation-release-trigger/logs/validation.txt +++ /dev/null @@ -1,45 +0,0 @@ -Validation commands for task 088 - -1) YAML parse -Command: -python -c "import yaml,sys; [yaml.safe_load(open(path, 'r', encoding='utf-8')) for path in sys.argv[1:]]; print('YAML OK')" ".github/workflows/update-winget.yml" ".github/workflows/reusable-release.yml" - -Output: -YAML OK - -2) Stable release metadata lookup -Command: -gh api "repos/NeuralNomadsAI/CodeNomad/releases/tags/v0.17.0" --jq '"id=\(.id) tag=\(.tag_name) draft=\(.draft) prerelease=\(.prerelease)"' - -Output: -id=336185327 tag=v0.17.0 draft=false prerelease=false - -3) Prerelease metadata lookup -Command: -gh api "repos/NeuralNomadsAI/CodeNomad/releases/tags/v0.17.1-dev-20260609-4a1d53bf" --jq '"id=\(.id) tag=\(.tag_name) draft=\(.draft) prerelease=\(.prerelease)"' - -Output: -id=336372359 tag=v0.17.1-dev-20260609-4a1d53bf draft=false prerelease=true - -4) Live asset resolver run -Command: -$releaseId = gh api "repos/NeuralNomadsAI/CodeNomad/releases/tags/v0.17.0" --jq ".id"; node scripts/winget/resolve-release-asset.cjs --repo "NeuralNomadsAI/CodeNomad" --release-id "$releaseId" --tag "v0.17.0" --timeout-seconds 5 --poll-interval-seconds 1 --json - -Output: -Resolved release id: 336185327 -{ - "asset_name": "CodeNomad-Tauri-windows-x64-0.17.0.zip", - "asset_regex": "^CodeNomad-Tauri-windows-x64-0\\.17\\.0\\.zip$", - "asset_sha256": "4E030C61728447E9D988C6C3F99745D30716B024588F0C626BF44A4607AD1071", - "asset_url": "https://github.com/NeuralNomadsAI/CodeNomad/releases/download/v0.17.0/CodeNomad-Tauri-windows-x64-0.17.0.zip", - "release_url": "https://github.com/NeuralNomadsAI/CodeNomad/releases/tag/v0.17.0", - "tag_name": "v0.17.0", - "version": "0.17.0" -} - -5) nomadworks_validate -Command: -nomadworks_validate - -Output: -undefined is not an object (evaluating 'res.warnings.length') diff --git a/evidences/088-fix-winget-automation-release-trigger/screenshots/README.md b/evidences/088-fix-winget-automation-release-trigger/screenshots/README.md deleted file mode 100644 index d0474e28e..000000000 --- a/evidences/088-fix-winget-automation-release-trigger/screenshots/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Screenshots - -No UI changed in task 088, so screenshots are not applicable.