Repository navigation
448 lines (428 loc) · 23.1 KB
/
Copy pathrelease.yml
File metadata and controls
448 lines (428 loc) · 23.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
name: Release
# E2E-GATED RELEASE, two channels, one pipeline (docs/branding-and-distribution.md §1):
#
# tag v* / dispatch ─► tests ─► E2E gate (real Windows VM) ─► build + publish
# nightly GUI E2E green on main (workflow_run) ────────────► build + publish
# (auto-vYYYYMMDD-<sha>, pre-release)
#
# The tagged channel proves the exact tag with a fresh E2E run. The auto
# channel's gate is the green nightly that triggered it: the release is cut
# from the exact SHA the nightly proved, so no additional VM time is burned.
# No green nightly, no auto release. wootc writes to a stranger's only
# computer — "the unit tests passed" is not a sufficient bar to ship on.
#
# Every release ships the shared boot chain, the native WinUI setup assets,
# and one Wails compatibility executable. Earlier releases published wootc.exe
# alone, leaving deployerBaseURL() pointing at assets that never existed (#196).
on:
push:
tags: ['v*']
workflow_run:
workflows: ['E2E GUI-driven (publish timelapse)']
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
release_tag:
description: 'Publish as this tag (e.g. v0.1.0) — a FULL release, created by the workflow itself. Empty = manual-v* pre-release.'
type: string
default: ''
skip_e2e:
description: 'Emergency: publish WITHOUT the E2E gate (documents itself in the release notes)'
type: boolean
default: false
permissions:
contents: read
packages: read # granted through to the reusable e2e-hosted (base-image pull)
actions: read # e2e-hosted's native jobs list this run's artifacts
concurrency:
group: release-${{ github.event_name == 'workflow_run' && 'auto' || github.ref }}
cancel-in-progress: false
jobs:
# ── 1. cheap signal first: never burn ~90 min of VM on a broken tree ───────
# Tag/dispatch channel only — the auto channel's SHA already passed the
# nightly, which runs these same suites en route.
tests:
name: fast + integration
if: ${{ github.event_name != 'workflow_run' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with: { submodules: recursive }
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with: { go-version: '1.27', cache: false }
# Before ~90 min of VM: a final tag without its narrative notes, or
# with notes still marked draft, can never publish (#242).
- name: Narrative release notes gate
env:
REQUESTED_TAG: ${{ inputs.release_tag }}
run: |
tag="${REQUESTED_TAG}"
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then tag="${GITHUB_REF_NAME}"; fi
if [ -n "$tag" ]; then bash tools/release/check-notes.sh "$tag"; fi
- run: sudo apt-get update -qq && sudo apt-get install -y -qq bats
- run: bash tests/run.sh fast
- uses: ./.github/actions/podman-runtime
- run: bash tests/migration/test-bridge.sh
# ── 2. the gate: GUI native install and graduation from Windows ───────────
e2e-gate:
name: E2E gate (real Windows VM)
needs: tests
if: ${{ github.event_name != 'workflow_run' && !inputs.skip_e2e }}
uses: ./.github/workflows/e2e-hosted.yml
with:
# Gate on the shipped WinUI shell, not the one-release Wails fallback.
# This is the same real GUI path that the setup assets will expose.
# The alpha image is proven through the REAL GUI before publishing.
image: ghcr.io/projectbluefin/bluefin:stable
win_version: '11'
bitlocker: 'off'
gui_install: true
gui_frontend: native
phase3: true
# ── 3. publish — only reachable when a gate passed (or was waived) ────────
publish:
name: build + publish
permissions:
contents: write
packages: read
actions: write # dispatch native packaging and winget
# GitHub OIDC for Azure Trusted Signing (#229). This job only: the
# token proves "the release job of tuna-os/wootc", and no other job
# needs to prove that. No Azure configuration = never requested.
id-token: write
# The federated credential's subject. Tag pushes, dispatches and the auto
# channel all run under different refs; the environment is one stable
# claim (repo:tuna-os/wootc:environment:release) for all of them.
environment: release
needs: [tests, e2e-gate]
if: >-
${{ always() && (
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') ||
(github.event_name != 'workflow_run' && needs.tests.result == 'success' &&
(needs.e2e-gate.result == 'success' || inputs.skip_e2e))
) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
submodules: recursive
# Auto channel: build the exact SHA the nightly proved, not
# whatever main has moved to since.
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref }}
# Reclaim BEFORE the tool setups, and never touch the toolcache: the
# first publish run (32557070841) deleted $AGENT_TOOLSDIRECTORY and
# /usr/local/lib/node_modules AFTER setup-node had installed Node
# there — npm vanished, the brand build died, and a PASSED E2E gate
# was thrown away one job later. dotnet/android/ghc/boost/swift free
# ~25 GB, which is all the deployer image build needs.
- name: Reclaim disk (the deployer image build needs the room)
run: |
df -h / || true
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
/usr/local/share/boost /usr/share/swift || true
sudo apt-get clean
docker image prune -af 2>/dev/null || true
echo "--- after cleanup ---"; df -h /
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with: { go-version: '1.27', cache: false }
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with: { node-version: '26' }
- uses: ./.github/actions/podman-runtime
- name: Resolve channel + tag
id: chan
env:
REQUESTED_TAG: ${{ inputs.release_tag }}
run: |
# Reject output-file/manifest injection as well as malformed tags.
tag="${REQUESTED_TAG}"
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then tag="${GITHUB_REF_NAME}"; fi
if [ -n "$tag" ] && [[ ! "$tag" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo '::error::Expected a release tag such as v1.2.3 or v1.2.3-rc.1'
exit 1
fi
if [ "${{ github.event_name }}" = "workflow_run" ]; then
sha="${{ github.event.workflow_run.head_sha }}"
echo "tag=auto-v$(date -u +%Y%m%d)-${sha::7}" >> "$GITHUB_OUTPUT"
echo "prerelease=true" >> "$GITHUB_OUTPUT"
echo "channel=auto" >> "$GITHUB_OUTPUT"
elif [ "${GITHUB_REF_TYPE}" = "tag" ]; then
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
echo "prerelease=false" >> "$GITHUB_OUTPUT"
echo "channel=tagged" >> "$GITHUB_OUTPUT"
elif [ -n "$REQUESTED_TAG" ]; then
# Dispatch with an explicit tag: a FULL release. The release
# action creates the tag at the dispatched SHA using the
# workflow's own token, so cutting a release needs no tag-push
# rights anywhere else — pick the branch, name the tag, and the
# same E2E gate stands in front of it.
echo "tag=$REQUESTED_TAG" >> "$GITHUB_OUTPUT"
echo "prerelease=false" >> "$GITHUB_OUTPUT"
echo "channel=tagged" >> "$GITHUB_OUTPUT"
else
# workflow_dispatch from a branch: synthesize a tag rather than
# publishing a release literally named "main".
echo "tag=manual-v$(date -u +%Y%m%d)-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
echo "prerelease=true" >> "$GITHUB_OUTPUT"
echo "channel=manual" >> "$GITHUB_OUTPUT"
fi
- name: Build deployer boot artifacts
# The same builds the E2E harness performs (tests/e2e/run-e2e.sh):
# deployer kernel+initramfs out of the deployer image, signed
# shim+GRUB out of a Fedora container, wubildr best-effort.
run: |
set -euo pipefail
podman build -t wootc-deployer -f payload/deployer/Containerfile .
for artifact in deployer-vmlinuz deployer-initramfs.img; do
podman run --rm --entrypoint /bin/cat wootc-deployer "/out/$artifact" \
> "release-assets/$artifact"
[ -s "release-assets/$artifact" ] || { echo "::error::empty $artifact"; exit 1; }
done
# The shim must be signed by a Microsoft UEFI CA the user's firmware
# actually holds, or the reboot ends at "bad shim signature" with
# nothing said beforehand (#322). Fedora 44 ships shim-x64-16.1-5,
# signed ONLY by the 2011 authority whose certificate expired
# 2026-06-27; 16.1-7 is dual-signed (2011 AND 2023), which boots on
# old and new firmware alike. Pinned by exact NVR because "latest"
# is how a single-signed build silently comes back. Drop the pin
# when a numbered Fedora ships a dual-signed shim of its own — the
# --require check below holds the line either way.
SHIM_NVR="shim-x64-16.1-7.x86_64"
SHIM_RPM="https://kojipkgs.fedoraproject.org/packages/shim/16.1/7/x86_64/${SHIM_NVR}.rpm"
# Copy out of /usr/lib/efi/shim/<ver>/ (where the package owns the
# files at every version seen so far), with the /boot/efi path the
# post-install scriptlet populates as the fallback.
CID=$(podman create quay.io/fedora/fedora:44 \
bash -c "dnf install -y -q '$SHIM_RPM' grub2-efi-x64 2>/dev/null && \
for f in shimx64.efi mmx64.efi; do \
cp /usr/lib/efi/shim/*/EFI/fedora/\$f /tmp/ 2>/dev/null || \
cp /boot/efi/EFI/fedora/\$f /tmp/; done && \
cp /boot/efi/EFI/fedora/grubx64.efi /tmp/ && echo DONE")
podman start -a "$CID"
podman cp "$CID:/tmp/shimx64.efi" release-assets/shimx64.efi
podman cp "$CID:/tmp/grubx64.efi" release-assets/grubx64.efi
# MokManager: shim launches it for the MOK enrollment that
# custom-kernel images (Bazzite, #248) queue during deploy. Signed
# by Fedora's own key and verified by shim, not by the firmware —
# so it is deliberately NOT graded against the Microsoft CAs.
podman cp "$CID:/tmp/mmx64.efi" release-assets/mmx64.efi
podman rm "$CID"
[ -s release-assets/shimx64.efi ] && [ -s release-assets/grubx64.efi ] \
&& [ -s release-assets/mmx64.efi ] || {
echo "::error::signed shim/GRUB extraction failed"; exit 1; }
# Record which Microsoft CA generations signed what we are shipping,
# and refuse to publish a shim that cannot boot on 2023-only
# firmware. The installer reads this file (or the value stamped into
# the exe below) to refuse BEFORE arming rather than after the
# reboot.
python3 packaging/shim-authorities.py --require 2023 --json \
release-assets/shimx64.efi > release-assets/shim-authorities.json
cat release-assets/shim-authorities.json
# wubildr is best-effort in the harness too — the signed shim chain
# is the fallback path.
if podman build -t wootc-wubildr -f payload/wubildr/Containerfile .; then
podman run --rm --entrypoint /bin/cat wootc-wubildr /out/wubildr.efi \
> release-assets/wubildr.efi || rm -f release-assets/wubildr.efi
fi
ls -lh release-assets/
working-directory: ${{ github.workspace }}
- name: Create a signing key for this release only
run: |
set -euo pipefail
key_dir=$(mktemp -d "$RUNNER_TEMP/wootc-signing.XXXXXX")
(cd app && go run ./tools/signmanifest generate "$key_dir/seed" "$key_dir/public.hex")
echo "WOOTC_SIGNING_DIR=$key_dir" >> "$GITHUB_ENV"
- name: Build brand installers (Wails compatibility asset)
env:
RELEASE_TAG: ${{ steps.chan.outputs.tag }}
# Phase D makes the native setup package the branded release asset.
# Keep exactly one Wails build for rollback and one-release migration;
# do not publish a second set of branded Wails frontends.
run: |
set -euo pipefail
sudo apt-get update -qq && sudo apt-get install -y -qq librsvg2-bin
(cd app/frontend && npm ci && npm run build)
mkdir -p release-assets
[ -n "$RELEASE_TAG" ] || { echo "::error::no release tag to pin the boot artifacts to"; exit 1; }
SHIM_AUTHORITIES=$(python3 -c "import json;print(','.join(json.load(open('release-assets/shim-authorities.json'))['shimx64.efi']))")
[ -n "$SHIM_AUTHORITIES" ] || { echo "::error::the staged shim carries no Microsoft UEFI CA"; exit 1; }
python3 packaging/build-windows.py --brand wootc \
--output release-assets/wootc-legacy.exe --version "$RELEASE_TAG" \
--tags desktop,production \
--manifest-public-key "$WOOTC_SIGNING_DIR/public.hex" \
--ldflags="-X main.shimAuthorities=$SHIM_AUTHORITIES"
ls -lh release-assets/wootc-legacy.exe
# ORDER IS LOAD-BEARING: sign after every exe is built and BEFORE the
# checksums. Authenticode rewrites the file, so SHA256SUMS written first
# would describe bytes nobody downloads and every install would die at
# the installer's fail-closed verification (#53). tests/unit/
# release-pipeline.bats pins this order.
# Azure Trusted Signing (#229): log in with the job's OIDC token, no
# stored secret. Skipped unless the store type is TRUSTEDSIGNING and
# AZURE_CLIENT_ID is set, so an unconfigured repo still ships unsigned.
# continue-on-error hands a failed login to sign-exes.sh, which applies
# the channel rule: a tagged release stops, a pre-release says so.
- name: Azure login for Trusted Signing (OIDC)
id: azure-login
if: ${{ vars.WOOTC_SIGN_STORETYPE == 'TRUSTEDSIGNING' && vars.AZURE_CLIENT_ID != '' }}
continue-on-error: true
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Authenticode-sign the installers (#230)
id: authenticode
env:
CHANNEL: ${{ steps.chan.outputs.channel }}
# Trusted Signing over OIDC: sign-exes.sh fetches the access token
# itself, immediately before signing (it expires in about an hour),
# and uses it in place of WOOTC_SIGN_STOREPASS.
WOOTC_SIGN_AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
WOOTC_SIGN_AZURE_LOGIN: ${{ steps.azure-login.outcome }}
# The credential is #229's decision; jsign speaks all of them.
# Unset WOOTC_SIGN_STORETYPE = not configured = ships unsigned.
WOOTC_SIGN_STORETYPE: ${{ vars.WOOTC_SIGN_STORETYPE }}
WOOTC_SIGN_KEYSTORE: ${{ vars.WOOTC_SIGN_KEYSTORE }}
WOOTC_SIGN_ALIAS: ${{ vars.WOOTC_SIGN_ALIAS }}
WOOTC_SIGN_TSAURL: ${{ vars.WOOTC_SIGN_TSAURL }}
WOOTC_SIGN_PUBLISHER: ${{ vars.WOOTC_SIGN_PUBLISHER }}
WOOTC_SIGN_STOREPASS: ${{ secrets.WOOTC_SIGN_STOREPASS }}
# PEM text, not paths: a repository variable cannot name a file on
# the runner. CA_PEM adds the code-signing and timestamp roots to
# the verify trust store. The Ubuntu bundle holds TLS roots only;
# Trusted Signing chains to "Microsoft Identity Verification Root
# Certificate Authority 2020", which is not in it; sign-exes.sh adds
# that root from packaging/authenticode-roots/ for TRUSTEDSIGNING,
# so CA_PEM is for other credentials. CERTCHAIN_PEM is
# jsign --certfile, which some HSM store types need.
WOOTC_SIGN_CA_PEM: ${{ vars.WOOTC_SIGN_CA_PEM }}
WOOTC_SIGN_CERTCHAIN_PEM: ${{ vars.WOOTC_SIGN_CERTCHAIN_PEM }}
JSIGN_VERSION: '7.5'
JSIGN_SHA256: 602a51c3545a6dc4fb99bd2ea7152b26d1345916d0c93ddfbd5936cb735af91c
run: |
set -euo pipefail
if [ -n "$WOOTC_SIGN_STORETYPE" ]; then
sudo apt-get install -y -qq osslsigncode
jar="$RUNNER_TEMP/jsign.jar"
curl -fsSL -o "$jar" \
"https://github.com/ebourg/jsign/releases/download/$JSIGN_VERSION/jsign-$JSIGN_VERSION.jar"
echo "$JSIGN_SHA256 $jar" | sha256sum -c -
printf '#!/bin/sh\nexec java -jar %s "$@"\n' "$jar" > "$RUNNER_TEMP/jsign"
chmod +x "$RUNNER_TEMP/jsign"
export JSIGN="$RUNNER_TEMP/jsign"
if [ -n "$WOOTC_SIGN_CA_PEM" ]; then
cat /etc/ssl/certs/ca-certificates.crt > "$RUNNER_TEMP/sign-ca.pem"
printf '%s\n' "$WOOTC_SIGN_CA_PEM" >> "$RUNNER_TEMP/sign-ca.pem"
export WOOTC_SIGN_CAFILE="$RUNNER_TEMP/sign-ca.pem"
fi
if [ -n "$WOOTC_SIGN_CERTCHAIN_PEM" ]; then
printf '%s\n' "$WOOTC_SIGN_CERTCHAIN_PEM" > "$RUNNER_TEMP/sign-chain.pem"
export WOOTC_SIGN_CERTFILE="$RUNNER_TEMP/sign-chain.pem"
fi
fi
bash packaging/sign-exes.sh "$CHANNEL" release-assets
- name: Checksums for the release assets
# The installer's fail-closed verification (#53/#194) and the
# getting-started doc both point users at a SHA256SUMS on the
# release. Publishing assets without one broke that promise — the
# live release 404'd it and every install died at "cannot verify
# boot artifacts" (run 32549251225).
run: |
set -euo pipefail
(cd release-assets && sha256sum * > SHA256SUMS)
(cd app && go run ./tools/signmanifest sign "$WOOTC_SIGNING_DIR/seed" ../release-assets/SHA256SUMS ../release-assets/SHA256SUMS.sig)
(cd app && go run ./tools/signmanifest verify "$WOOTC_SIGNING_DIR/public.hex" ../release-assets/SHA256SUMS ../release-assets/SHA256SUMS.sig)
cp "$WOOTC_SIGNING_DIR/public.hex" release-assets/artifact-public-key.hex
- name: Remove the private signing seed
if: always()
run: |
if [ -n "${WOOTC_SIGNING_DIR:-}" ]; then
rm -f "$WOOTC_SIGNING_DIR/seed"
fi
- name: Record what gated this build
env:
RELEASE_TAG: ${{ steps.chan.outputs.tag }}
SIGNING: ${{ steps.authenticode.outputs.signing }}
run: |
{
echo "## wootc $RELEASE_TAG"
echo
if [ "${{ steps.chan.outputs.channel }}" = "auto" ]; then
echo "Automated pre-release, cut from the exact commit the nightly"
echo "GUI-driven E2E just proved: a real Windows 11 VM installed this"
echo "build through the GUI and booted natively into Linux."
echo "See the gate run for its selected stages and final OS."
echo
echo "Gate run: ${{ github.event.workflow_run.html_url }}"
elif [ "${{ inputs.skip_e2e }}" = "true" ]; then
echo "> ⚠️ **Published WITHOUT the E2E gate** (\`skip_e2e\`). Not verified"
echo "> against a real Windows VM."
else
echo "Gated on GUI native installation and graduation: a real"
echo "Windows 11 VM installed this build, booted Linux from"
echo "\`root.disk\`, then booted the graduated native disk."
echo "Gate run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
fi
case "$SIGNING" in
signed) ;;
failed)
echo
echo "> ⚠️ **Not code-signed — signing FAILED for this build.** The exes are"
echo "> unsigned: SmartScreen and UAC will show an unknown publisher." ;;
*)
echo
echo "> ⚠️ **Not code-signed** (#229). SmartScreen and UAC will show an"
echo "> unknown publisher." ;;
esac
echo
echo "Native WinUI setup assets are the primary branded downloads:"
echo "\`<Product>-Installer-Setup.exe\` for every eligible brand."
echo "The WinUI packaging workflow attaches them with native-SHA256SUMS."
echo "The Wails compatibility build remains for this release only as"
echo "\`wootc-legacy.exe\`, so a rollback has a named, non-primary path."
echo
echo "Walkthrough: https://tuna-os.github.io/wootc/e2e/latest/"
# The narrative (#242): docs/release-notes-<tag>.md when it
# exists. check-notes.sh fails on a draft, which stops the publish.
notes="$(bash tools/release/check-notes.sh "$RELEASE_TAG")"
if [ -n "$notes" ]; then
echo
echo "---"
echo
cat "$notes"
fi
} > NOTES.md
cat NOTES.md
- uses: softprops/action-gh-release@8a0cd651e5b2365bd640854a3fa5dfc70e4875f3 # v3
with:
tag_name: ${{ steps.chan.outputs.tag }}
target_commitish: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || (github.ref_type != 'tag' && github.sha || '') }}
prerelease: ${{ steps.chan.outputs.prerelease }}
files: release-assets/*
body_path: NOTES.md
draft: false
- name: Dispatch native setup packaging
# The release has to exist before winui-preview.yml can fetch the
# public half of artifact-public-key.hex. It builds and attaches the
# setup assets at this exact tag; winget waits for the primary generic
# setup below.
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.chan.outputs.tag }}
run: |
gh workflow run winui-preview.yml --repo "${{ github.repository }}" \
--ref "$RELEASE_TAG" -f release_tag="$RELEASE_TAG"
- name: Dispatch winget submission (full releases only)
# winget-publish.yml listens on release:published, but releases cut
# here are created with GITHUB_TOKEN, and GitHub never triggers
# workflows from events its own token caused (recursion guard) — the
# v0.1.0-alpha.1 publish proved it: 0 winget runs. An explicit
# workflow_dispatch through the API DOES fire, so hand it the tag.
if: ${{ steps.chan.outputs.prerelease == 'false' }}
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.chan.outputs.tag }}
run: |
gh workflow run winget-publish.yml --repo "${{ github.repository }}" \
-f tag="$RELEASE_TAG"