Skip to content

Commit a2e6031

Browse files
committed
chore: move winget submission to local OAuth
1 parent 2a6ec64 commit a2e6031

8 files changed

Lines changed: 220 additions & 152 deletions

File tree

.github/workflows/release.yml

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -47,60 +47,3 @@ jobs:
4747
releaseDraft: false
4848
prerelease: false
4949
updaterJsonPreferNsis: true
50-
51-
update-winget:
52-
needs: release
53-
runs-on: windows-latest
54-
permissions:
55-
contents: read
56-
env:
57-
PACKAGE_IDENTIFIER: xarthurx.Whisperi
58-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59-
WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_CREATE_GITHUB_TOKEN }}
60-
RELEASE_TAG: ${{ github.ref_name }}
61-
steps:
62-
- name: Validate Winget token
63-
shell: pwsh
64-
run: |
65-
if ([string]::IsNullOrWhiteSpace($env:WINGET_CREATE_GITHUB_TOKEN)) {
66-
Write-Warning "WINGET_CREATE_GITHUB_TOKEN not configured — skipping Winget submission."
67-
exit 0
68-
}
69-
70-
- name: Install .NET 6
71-
uses: actions/setup-dotnet@v4
72-
with:
73-
dotnet-version: '6.0.x'
74-
75-
- name: Download wingetcreate
76-
shell: pwsh
77-
run: Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
78-
79-
- name: Submit Winget manifest update
80-
shell: pwsh
81-
run: |
82-
$headers = @{
83-
Authorization = "Bearer $env:GITHUB_TOKEN"
84-
Accept = "application/vnd.github+json"
85-
"X-GitHub-Api-Version" = "2022-11-28"
86-
}
87-
88-
$releaseApiUrl = "https://api.github.com/repos/${{ github.repository }}/releases/tags/$env:RELEASE_TAG"
89-
Write-Output "Fetching release from: $releaseApiUrl"
90-
$release = Invoke-RestMethod -Headers $headers -Uri $releaseApiUrl
91-
92-
$tag = $release.tag_name
93-
$version = $tag -replace '^v', ''
94-
$assets = @($release.assets)
95-
$installerAssets = @($assets | Where-Object { $_.name -like '*_x64-setup.exe' })
96-
97-
if ($installerAssets.Count -ne 1) {
98-
throw "Expected exactly one x64 setup asset on release $tag, found $($installerAssets.Count)."
99-
}
100-
101-
.\wingetcreate.exe update `
102-
$env:PACKAGE_IDENTIFIER `
103-
-u $installerAssets[0].browser_download_url `
104-
-v $version `
105-
-t $env:WINGET_CREATE_GITHUB_TOKEN `
106-
--submit

.github/workflows/update-winget.yml

Lines changed: 0 additions & 75 deletions
This file was deleted.

CLAUDE.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,35 @@ Tauri 2.10+, React 19, TypeScript (strict), Tailwind CSS v4, shadcn/ui, i18next
4343

4444
## Winget Manifests
4545

46-
- `wingetcreate update --submit` in CI handles manifest generation; metadata is **inherited verbatim from the previous version's manifest in `microsoft/winget-pkgs`**. Once a bad field lands, every future submission propagates it — the rules below must be enforced by patching the PR, not by hoping `wingetcreate` will fix it.
46+
WinGet submission is local because Microsoft's open-source enterprise limits classic PATs to eight days and WinGetCreate does not support fine-grained PATs. Never add a WinGet PAT back to GitHub Actions or pass a token with WinGetCreate's `--token` argument.
47+
48+
### New Windows Machine Setup
49+
50+
Run these commands once from the Whisperi repository:
51+
52+
```powershell
53+
winget install --id Microsoft.WingetCreate --exact --source winget --accept-source-agreements --accept-package-agreements
54+
wingetcreate token -s
55+
```
56+
57+
- `wingetcreate token -s` starts GitHub's OAuth flow and stores the resulting credential in WinGetCreate's local cache for the current Windows user. If a browser, device code, or authorization prompt appears, the agent must pause and ask the user to approve it; never attempt to extract, print, or copy the cached credential.
58+
- A successful setup prints `Token stored in cache successfully.` Repeat it only on a new machine/user profile, after the cache is cleared, or when GitHub revokes the authorization.
59+
60+
### Per-Release Submission
61+
62+
Only submit after the non-draft GitHub release and its signed x64 NSIS asset are public. Run preview first, then submit exactly once:
63+
64+
```powershell
65+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/submit-winget.ps1 vX.Y.Z -Preview
66+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/submit-winget.ps1 vX.Y.Z
67+
```
68+
69+
- If submission reports an authentication problem, refresh the cache with `wingetcreate token -s` and retry. Do not create a classic or fine-grained PAT.
70+
- `scripts/submit-winget.ps1` handles manifest generation and submission with WinGetCreate's cached OAuth credential; metadata is **inherited verbatim from the previous version's manifest in `microsoft/winget-pkgs`**. Once a bad field lands, every future submission propagates it — the rules below must be enforced by patching the PR, not by hoping `wingetcreate` will fix it.
4771
- **License**: SPDX identifier `MIT` (not `MIT License`), and include `LicenseUrl: https://github.com/xarthurx/whisperi/blob/main/LICENSE`. Copilot review flags `MIT License` as non-SPDX (precedent: [PR #376335](https://github.com/microsoft/winget-pkgs/pull/376335))
4872
- **ShortDescription**: single concise phrase only (~one line); longer text goes in `Description`
4973
- **`ReleaseDate`** in installer manifest is **valid** (schema 1.2.0+, see [installer schema 1.12.0](https://github.com/microsoft/winget-pkgs/blob/master/doc/manifest/schema/1.12.0/manifest.installer.1.12.0.json)) — Copilot has incorrectly flagged this as needing to move to the version manifest; don't move it
50-
- **After every release tag**, when the CI-submitted winget PR appears under `microsoft/winget-pkgs`: open the generated `*.locale.en-US.yaml` and verify all four rules above before letting it merge. Push fixes onto the PR branch (`xarthurx.Whisperi-<version>-<uuid>` on `xarthurx/winget-pkgs`) — do not wait for the next version, since the next `wingetcreate` run will re-inherit whatever is in the latest accepted manifest.
74+
- **After every local WinGet submission**, open the generated PR under `microsoft/winget-pkgs` and verify all four rules above before letting it merge. Push fixes onto the PR branch (`xarthurx.Whisperi-<version>-<uuid>` on `xarthurx/winget-pkgs`) — do not wait for the next version, since the next `wingetcreate` run will re-inherit whatever is in the latest accepted manifest.
5175
- See [docs/PROGRESS.md](docs/PROGRESS.md) for full winget notes
5276

5377
## Workflow Rules

docs/ARCHITECTURE.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,19 @@ Single `check-and-build` job: TypeScript check → Vite build → `cargo test`
297297

298298
### Release Pipeline (`.github/workflows/release.yml`)
299299

300-
Triggered on version tags (`v*`). Two jobs:
301-
1. **`release`** — Builds the NSIS installer via `tauri-apps/tauri-action@v0` and publishes it as a GitHub Release asset. Windows-only.
302-
2. **`update-winget`** — Runs after `release`. Downloads `wingetcreate`, resolves the released x64 NSIS installer asset, and submits a manifest update PR for `xarthurx.Whisperi` to `microsoft/winget-pkgs`. Requires a `WINGET_CREATE_GITHUB_TOKEN` classic PAT with `public_repo` scope (≤ 90-day lifetime).
300+
Triggered on version tags (`v*`). Its Windows `release` job builds and signs the NSIS/MSI installers via `tauri-apps/tauri-action@v0`, then publishes them with updater metadata as a GitHub Release.
303301

304-
The Winget step is in the same workflow (not a separate one) because `tauri-action` creates the release using `GITHUB_TOKEN`, and events from `GITHUB_TOKEN` do not trigger other workflows.
302+
### WinGet Update (Local OAuth)
305303

306-
### Winget Update (Manual) (`.github/workflows/update-winget.yml`)
304+
WinGet submission is deliberately separate from GitHub Actions. Microsoft's open-source enterprise limits classic PATs to eight days, while WinGetCreate does not support fine-grained PATs for cross-owner public-repository contributions. The release workstation uses WinGetCreate's cached OAuth login instead:
307305

308-
Manual-only (`workflow_dispatch`) backup for retries and backfills against an existing release tag.
306+
```powershell
307+
wingetcreate token -s
308+
powershell -ExecutionPolicy Bypass -File scripts/submit-winget.ps1 vX.Y.Z -Preview
309+
powershell -ExecutionPolicy Bypass -File scripts/submit-winget.ps1 vX.Y.Z
310+
```
311+
312+
The submission script resolves the published release through GitHub's public API, verifies there is exactly one x64 NSIS installer, generates the three manifests in a temporary directory, checks the inherited metadata and GitHub asset digest, then invokes `wingetcreate submit` without putting a token on the command line. The temporary directory is removed on success or failure.
309313

310314
### Key Dependencies
311315

@@ -405,13 +409,15 @@ whisperi/
405409
├── docs/
406410
│ ├── ARCHITECTURE.md # This file
407411
│ ├── CHANGELOG.md # Version history
408-
│ └── CONTINUE.md # Short follow-up notes
412+
│ ├── PROGRESS.md # Operational/release notes
413+
│ └── TODO.md # Follow-up work
414+
├── scripts/
415+
│ └── submit-winget.ps1 # Local OAuth WinGet submission
409416
├── .github/
410417
│ ├── README.md # GitHub repo readme
411418
│ └── workflows/
412419
│ ├── ci.yml # CI pipeline (push/PR)
413-
│ ├── release.yml # Release pipeline (version tags)
414-
│ └── update-winget.yml # Winget submission on published releases
420+
│ └── release.yml # Release pipeline (version tags)
415421
├── package.json # Frontend deps + scripts
416422
└── CLAUDE.md # Claude Code project instructions
417423
```

docs/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [Unreleased]
4+
5+
### Internal
6+
7+
- Moved WinGet publication out of GitHub Actions and into a local, cached-OAuth release script. The script resolves the published installer, generates manifests in temporary storage, validates their inherited metadata and asset hash, and submits without placing a personal access token on the command line. This avoids Microsoft's eight-day classic-PAT lifetime limit and prevents WinGet authentication from marking an otherwise successful app release as failed.
8+
- Documented the complete new-machine and per-release WinGet procedure for future maintainers and coding agents: install WinGetCreate, authorize its local OAuth cache once, preview every generated manifest, submit once, and pause for the user whenever GitHub requires interactive authorization.
9+
310
## [0.8.2] - 2026-07-18
411

512
### Highlights

docs/PROGRESS.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,30 @@
22

33
## Winget Submission
44

5-
### Token
5+
### Authentication
66

7-
- `WINGET_CREATE_GITHUB_TOKEN` must be a **classic PAT** with `public_repo` scope and **≤ 90-day** lifetime (enforced by the `Microsoft Open Source` GitHub enterprise policy — applies to both classic and fine-grained PATs).
7+
- WinGetCreate is installed locally through `winget install --id Microsoft.WingetCreate --exact`.
8+
- Authenticate once on the release workstation with `wingetcreate token -s`. WinGetCreate keeps the OAuth credential in its local cache; no token is passed on the command line or stored in the Whisperi repository.
9+
- Microsoft's open-source GitHub enterprise rejects classic PATs whose lifetime exceeds eight days. Fine-grained PATs are not a replacement because WinGetCreate and cross-owner public-repository contributions do not support them. The former `WINGET_CREATE_GITHUB_TOKEN` workflows were removed after this policy caused the v0.8.2 follow-up job to fail.
810

911
### Workflow
1012

11-
- Winget submission runs as the `update-winget` job in `release.yml`, triggered automatically after the build job on tag push (`v*`).
12-
- `update-winget.yml` is kept as a manual-only (`workflow_dispatch`) backup for retries/backfills.
13-
- The release is created by `tauri-action` using `GITHUB_TOKEN`, which does not trigger other workflows (GitHub security policy). That's why the Winget step is in the same workflow instead of a separate one.
13+
- `.github/workflows/release.yml` only builds, signs, and publishes the GitHub release. A WinGet credential failure can no longer mark the application release as failed.
14+
- After the release assets are public, preview the generated manifests with `powershell -ExecutionPolicy Bypass -File scripts/submit-winget.ps1 vX.Y.Z -Preview`.
15+
- Submit with `powershell -ExecutionPolicy Bypass -File scripts/submit-winget.ps1 vX.Y.Z`. The script resolves the exact published release, requires one x64 NSIS asset, generates manifests in a temporary directory, verifies the package/version/URL/hash/release date/license metadata, and submits them using the cached OAuth credential.
16+
- The script cleans its temporary output, so running it from the repository does not create untracked manifest files.
1417

1518
### wingetcreate Notes
1619

17-
`wingetcreate` (v1.12.8.0, framework-dependent, requires .NET 6) output is used as-is with `--submit`. Its `ReleaseDate` placement at the top level (outside `Installers`) looks wrong per the schema docs but is the convention winget-pkgs validation expects — do not move it inside the installer entry.
20+
`wingetcreate` v1.12.8.0 generates the manifests locally, then its `submit` command publishes the validated directory. Its `ReleaseDate` placement at the top level (outside `Installers`) looks wrong per the schema docs but is the convention winget-pkgs validation expects — do not move it inside the installer entry.
1821

1922
**Do NOT use the self-contained wingetcreate** (`aka.ms/wingetcreate/latest/self-contained`) — it bundles v1.0.4.0, which generates schema 1.1.0 manifests instead of 1.10.0.
2023

2124
### Manifest Metadata Quality (from PR #354548 Copilot review)
2225

2326
`wingetcreate update` inherits metadata from the previous version's manifest in winget-pkgs. Fix these once and they carry forward to all future versions:
2427

25-
- **License** — Use SPDX identifier `MIT` (not `MIT License`). Add `LicenseUrl: https://github.com/xarthurx/whisperi/blob/master/LICENSE`.
28+
- **License** — Use SPDX identifier `MIT` (not `MIT License`). Add `LicenseUrl: https://github.com/xarthurx/whisperi/blob/main/LICENSE`.
2629
- **ShortDescription** — Keep to a single concise phrase (e.g. `Lightweight Windows speech-to-text app.`). Move longer text to a separate `Description` field.
2730
- **Locale metadata** — Include `PublisherUrl`, `PublisherSupportUrl`, and `PackageUrl` for storefront quality.
2831
- **`ReleaseDate` in installer manifest** — Copilot flagged this as invalid, but it IS a valid field in the installer schema (added in 1.2.0+). `wingetcreate` generates it correctly. The PR was approved by a human reviewer — ignore this Copilot suggestion.

docs/TODO.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# TODO
22

3-
- **Renew `WINGET_CREATE_GITHUB_TOKEN`** — classic PAT with `public_repo` scope, ≤ 90-day lifetime (Microsoft Open Source enterprise policy). Last set **2026-03-16**; renew by **2026-06-13**.
4-
53
## Live mode stabilization
64

75
- [ ] Remove "(Beta)" label after 2 consecutive minor releases with zero Live-mode-related issues + multi-provider validation.

0 commit comments

Comments
 (0)