Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/reusable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ jobs:
release_name: ${{ needs.prepare-release.outputs.release_name }}
secrets: inherit

update-winget:
needs:
Comment thread
pascalandr marked this conversation as resolved.
- 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 }}
Expand Down
80 changes: 73 additions & 7 deletions .github/workflows/update-winget.yml
Original file line number Diff line number Diff line change
@@ -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' }}
Expand All @@ -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"
Expand Down Expand Up @@ -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 }}
11 changes: 7 additions & 4 deletions docs/guides/winget-release-automation.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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`.
Expand All @@ -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.
Loading