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
22 changes: 22 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -774,18 +774,39 @@ jobs:
if: needs.validate.outputs.prerelease == 'false'
runs-on: ubuntu-latest
steps:
- name: Check whether WebCodr.jwtd is already in winget-pkgs
id: pkg
env:
GH_TOKEN: ${{ github.token }}
run: |
# The WinGet update needs a merged base manifest as its template and
# must not open a second "New package" PR while the first submission
# is still under moderation. If WebCodr.jwtd is not yet in winget-pkgs
# main, skip the WinGet update: the first submission is reviewed out
# of band, and normal automation resumes on its own once it merges.
# Every other release channel is an independent job and is unaffected.
if gh api repos/microsoft/winget-pkgs/contents/manifests/w/WebCodr/jwtd >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "WebCodr.jwtd found in winget-pkgs; proceeding with the WinGet update."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "::notice::WebCodr.jwtd is not yet in winget-pkgs main; skipping the WinGet submission until the first-submission PR merges."
fi
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: steps.pkg.outputs.exists == 'true'
with:
name: jwtd-release-assets
path: artifacts
- name: Verify installers against checksums
if: steps.pkg.outputs.exists == 'true'
run: |
# Confirm the published windows zips (and every other asset) match the
# signed checksums.txt before komac builds the manifest from the same
# release URLs, so WinGet can only point at the exact binaries this
# release verified. Release assets are immutable once published.
(cd artifacts && sha256sum -c checksums.txt)
- name: Install komac
if: steps.pkg.outputs.exists == 'true'
env:
KOMAC_VERSION: 2.16.0
KOMAC_SHA256: 7d2707fa6210f2789a3702de49fbd150b736dbf426ee0b9bc8e098736f9fd82d
Expand All @@ -799,6 +820,7 @@ jobs:
tar -xzf "$asset" komac
install -Dm755 komac "$RUNNER_TEMP/bin/komac"
- name: Submit to winget-pkgs
if: steps.pkg.outputs.exists == 'true'
env:
# A fine-grained/classic PAT that can push to the webcodr/winget-pkgs
# fork and open a PR against microsoft/winget-pkgs. winget-pkgs PRs are
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Homebrew is published as a **formula**, not a cask. Casks quarantine their downl

Scoop is generated by GoReleaser: `scoops` renders `jwtd.json` with `skip_upload: true`, pinned to `ids: [jwtd]` so it uses the windows `tar.gz` (not the WinGet zip), and `update-scoop` cross-checks its hashes against `checksums.txt` before pushing to `webcodr/scoop-bucket`. `release.disable` means it needs an explicit `url_template`. Both downstream jobs run only for stable releases, after the release job succeeds.

WinGet is published to `microsoft/winget-pkgs` as `WebCodr.jwtd`, a portable-zip package: the windows `.zip` is consumed via `NestedInstallerType: portable` pointing at `jwtd.exe`. The manifest set is hand-templated in `winget/` (`WebCodr.jwtd.yaml`, `.installer.yaml`, `.locale.en-US.yaml`) with `VERSION`/`SHA256_WINDOWS_AMD64`/`SHA256_WINDOWS_ARM64` placeholders and is the source of truth used to seed the first submission manually (winget-pkgs moderates new packages). Thereafter `update-winget` verifies every release asset against the signed `checksums.txt`, installs a version-pinned and sha256-verified `komac`, and runs `komac update WebCodr.jwtd --version <v> --urls <zip urls> --submit` (with `KOMAC_FORK_OWNER=webcodr` and the `WINGET_TOKEN` secret) to open the winget-pkgs PR from the `webcodr/winget-pkgs` fork. komac uses the GitHub API (it never clones the huge winget-pkgs repo) and reuses the previously submitted manifest as its base. Nothing komac does reaches users without the moderated PR merging. Like the other downstream jobs it runs only for stable releases after the release job; `TestWinGetInvariants` enforces the portable-zip manifest contract and the gated, checksum-verified, pinned-komac job.
WinGet is published to `microsoft/winget-pkgs` as `WebCodr.jwtd`, a portable-zip package: the windows `.zip` is consumed via `NestedInstallerType: portable` pointing at `jwtd.exe`. The manifest set is hand-templated in `winget/` (`WebCodr.jwtd.yaml`, `.installer.yaml`, `.locale.en-US.yaml`) with `VERSION`/`SHA256_WINDOWS_AMD64`/`SHA256_WINDOWS_ARM64` placeholders and is the source of truth used to seed the first submission manually (winget-pkgs moderates new packages). Thereafter `update-winget` verifies every release asset against the signed `checksums.txt`, installs a version-pinned and sha256-verified `komac`, and runs `komac update WebCodr.jwtd --version <v> --urls <zip urls> --submit` (with `KOMAC_FORK_OWNER=webcodr` and the `WINGET_TOKEN` secret) to open the winget-pkgs PR from the `webcodr/winget-pkgs` fork. komac uses the GitHub API (it never clones the huge winget-pkgs repo) and reuses the previously submitted manifest as its base. Nothing komac does reaches users without the moderated PR merging. A guard step first checks whether `WebCodr.jwtd` already exists in winget-pkgs main (`gh api .../contents/manifests/w/WebCodr/jwtd`) and gates the komac install/submit steps on it: during the first-submission window — the package not yet merged, its "New package" PR still under moderation — the job skips cleanly instead of failing or opening a duplicate "New package" PR, and normal automation resumes once the first PR merges. Every other release channel is an independent job (`needs: [validate, release]`), so a skipped or failed WinGet update never blocks them. Like the other downstream jobs it runs only for stable releases after the release job; `TestWinGetInvariants` enforces the portable-zip manifest contract and the gated, checksum-verified, pinned-komac job, including that the submission is gated on the existence check.

Fedora is published to COPR (`webcodr/jwtd`) as a binary-repackage RPM, not a from-source build. `copr/jwtd.spec` carries a `VERSION`/`DATE`-placeholder spec that wraps the prebuilt linux archives (`Source0`/`Source1` per arch, selected with `%ifarch`), disables the debuginfo subpackage (`%global debug_package %{nil}`) since the Go binary is prebuilt, and ships the `LICENSE`. The `update-copr` job verifies the archives against the signed `checksums.txt`, renders the spec, builds a source RPM with `rpmbuild -bs` on the ubuntu runner (the SRPM step needs no Fedora macros), and submits it with `copr-cli build webcodr/jwtd <srpm> --nowait` authenticated by the base64-encoded `COPR_API_TOKEN` secret. COPR builds the SRPM in its Fedora chroots and signs the result with its own key. There is deliberately no version-downgrade guard: `dnf` resolves the highest EVR from the repo, so a re-submitted older version cannot downgrade users. `TestCOPRInvariants` enforces the binary-repackage contract and the gated job. Like the others, it runs only for stable releases after the release job.

Expand Down
21 changes: 21 additions & 0 deletions workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,8 @@ func TestGoReleaserSupplyChainInvariants(t *testing.T) {
// .github/workflows/release.yml.
type releaseWorkflowStep struct {
Name string `yaml:"name"`
ID string `yaml:"id"`
If string `yaml:"if"`
Uses string `yaml:"uses"`
Run string `yaml:"run"`
Env map[string]string `yaml:"env"`
Expand Down Expand Up @@ -992,6 +994,25 @@ func TestWinGetInvariants(t *testing.T) {
if got := submitStep.Env["GITHUB_TOKEN"]; !strings.Contains(got, "WINGET_TOKEN") {
t.Errorf("update-winget must authenticate with the WINGET_TOKEN secret, got %q", got)
}

// The first WebCodr.jwtd submission is moderated out of band, and komac
// update has no base manifest until it merges. A guard step checks whether
// the package is already in winget-pkgs and gates the submission on it, so a
// release cut during the first-submission window neither fails the job nor
// opens a duplicate "New package" PR.
guardStep := findStepContainingRun(wingetJob.Steps, "manifests/w/WebCodr/jwtd")
if guardStep == nil {
t.Fatal("update-winget must check whether WebCodr.jwtd exists in winget-pkgs before submitting")
}
if guardStep.ID == "" {
t.Fatal("the winget-pkgs existence check must have an id so later steps can gate on its output")
}
gate := "steps." + guardStep.ID + ".outputs.exists == 'true'"
for _, step := range []*releaseWorkflowStep{installStep, submitStep} {
if !strings.Contains(step.If, gate) {
t.Errorf("update-winget step %q must be gated on %q so it is skipped until the first submission merges, got if=%q", step.Name, gate, step.If)
}
}
}

// TestReleaseWorkflowSecurityInvariants checks the durable security
Expand Down