ci(publish): wire winget job via komac (D3 §3d)
Add the 4th publish.yaml sibling job: opens a PR to microsoft/winget-pkgs with komac (pinned 2.16.0 prebuilt) for LazyEvaluation.RdbmsPlayground (portable, x64 + arm64). Unlike scoop/homebrew it's a PR into Microsoft's central, human-gated catalog - async and validated on their side. - Auth: a classic public_repo GitHub PAT on a dedicated bot account (lazyeval-ci; fine-grained tokens can't open the cross-fork PR - komac #310), as the WINGET_GITHUB_TOKEN secret, job-scoped and passed to the API guards via a 0600 curl config file (never argv). - Idempotent via two guards before submitting (already-merged version + already-open PR) so a repeated publish dispatch can't open a duplicate. - No actions/checkout (komac works off URLs + the GitHub API). Docs: ADR-0056 Amendment 4 - the model, the one-time manual `komac new` bootstrap recipe (flags verified vs komac 2.16.0), and first-run learnings: the fork must pre-exist, and a direct single-exe portable takes its PATH alias from Commands[0] (not PortableCommandAlias, which is nested-only). Plus README index + requirements D3. Wiring only; going live needs the bootstrap PR (#391335, submitted) to merge.
This commit is contained in:
@@ -198,6 +198,87 @@ jobs:
|
|||||||
git push origin HEAD:main
|
git push origin HEAD:main
|
||||||
echo "homebrew: tap updated to rdbms-playground $VER."
|
echo "homebrew: tap updated to rdbms-playground $VER."
|
||||||
|
|
||||||
# winget remains a future sibling job here (komac on Linux CI, or a manual PR
|
# Update the winget package (Windows) by opening a PR to microsoft/winget-pkgs
|
||||||
# to microsoft/winget-pkgs). No `needs:` between jobs — each is independent and
|
# with komac. Unlike scoop-bucket/homebrew-tap (which push to OUR repos and are
|
||||||
# idempotent, so one failing or being added never breaks another.
|
# live at once), winget is a PR into Microsoft's central, human-gated catalog —
|
||||||
|
# asynchronous, and re-submitting the same version would open a DUPLICATE PR. So
|
||||||
|
# this job guards on both already-merged versions AND already-open PRs before
|
||||||
|
# submitting, which keeps a repeated `publish` dispatch safe.
|
||||||
|
#
|
||||||
|
# Auth: komac needs a CLASSIC GitHub PAT with `public_repo` (fine-grained tokens
|
||||||
|
# cannot open the cross-fork PR — komac #310). It is held on a dedicated GitHub
|
||||||
|
# bot account (a leak can't reach other repos — same reasoning as lazyeval-ci)
|
||||||
|
# and referenced ONLY in this job's env (job-level secret scoping keeps it out
|
||||||
|
# of the other publish jobs).
|
||||||
|
#
|
||||||
|
# PREREQUISITE: the package `LazyEvaluation.RdbmsPlayground` must already exist
|
||||||
|
# in winget-pkgs via a one-time `komac new` (interactive — run manually once;
|
||||||
|
# see ADR-0056 Amendment 4). This job only does the per-release `komac update`.
|
||||||
|
winget:
|
||||||
|
runs-on: ci-public
|
||||||
|
container:
|
||||||
|
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||||
|
steps:
|
||||||
|
- name: submit the winget update PR (idempotent)
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
# Classic public_repo PAT for the winget bot account. komac reads it
|
||||||
|
# from GITHUB_TOKEN; the API guards below read it via a curl config file
|
||||||
|
# so the token never appears in a command line / process list.
|
||||||
|
GITHUB_TOKEN: ${{ secrets.WINGET_GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
VER="${TAG#v}"
|
||||||
|
PKG="LazyEvaluation.RdbmsPlayground"
|
||||||
|
echo "winget: targeting $PKG $VER ($TAG)"
|
||||||
|
|
||||||
|
# Auth header in a 0600 curl config (keeps the token out of argv/logs).
|
||||||
|
umask 077
|
||||||
|
printf 'header = "Authorization: Bearer %s"\nheader = "Accept: application/vnd.github+json"\n' \
|
||||||
|
"$GITHUB_TOKEN" > /tmp/gh-curlrc
|
||||||
|
api="https://api.github.com"
|
||||||
|
|
||||||
|
# Guard 1 — already merged into winget-pkgs?
|
||||||
|
# (manifests/<first-letter>/<Publisher>/<Package>/<version>)
|
||||||
|
merged=$(curl -sS -o /dev/null -w '%{http_code}' --config /tmp/gh-curlrc \
|
||||||
|
"$api/repos/microsoft/winget-pkgs/contents/manifests/l/LazyEvaluation/RdbmsPlayground/$VER" || echo 000)
|
||||||
|
if [ "$merged" = "200" ]; then
|
||||||
|
echo "winget: $PKG $VER already in winget-pkgs — nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Guard 2 — an open PR for this exact id+version already? (avoid a dup)
|
||||||
|
# curl -G --data-urlencode does the URL-encoding (no jq in the image).
|
||||||
|
curl -sS -G --config /tmp/gh-curlrc \
|
||||||
|
--data-urlencode "q=repo:microsoft/winget-pkgs type:pr state:open in:title \"$PKG\" \"$VER\"" \
|
||||||
|
"$api/search/issues" -o /tmp/winget-search.json
|
||||||
|
open=$(node -e 'process.stdout.write(String(JSON.parse(require("fs").readFileSync("/tmp/winget-search.json","utf8")).total_count||0))')
|
||||||
|
if [ "$open" != "0" ]; then
|
||||||
|
echo "winget: an open PR for $PKG $VER already exists — skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install pinned komac (prebuilt glibc binary; the CI image has no
|
||||||
|
# cargo/komac). Pinned for reproducibility — bump deliberately.
|
||||||
|
KOMAC_VER=2.16.0
|
||||||
|
curl -fsSL -o /tmp/komac.tgz \
|
||||||
|
"https://github.com/russellbanks/Komac/releases/download/v$KOMAC_VER/komac-$KOMAC_VER-x86_64-unknown-linux-gnu.tar.gz"
|
||||||
|
tar -xzf /tmp/komac.tgz -C /tmp
|
||||||
|
komac_bin=$(find /tmp -maxdepth 2 -type f -name komac | head -1)
|
||||||
|
[ -n "$komac_bin" ] || { echo "ERROR: komac binary not found after extract" >&2; exit 1; }
|
||||||
|
|
||||||
|
base="https://git.lazyeval.net/oli/rdbms-playground/releases/download/$TAG"
|
||||||
|
echo "winget: submitting update PR via komac $KOMAC_VER"
|
||||||
|
# NB: confirm flags against `komac update --help` on first run — komac
|
||||||
|
# evolves; --version/--urls/--submit are the stable core. komac infers
|
||||||
|
# architecture + the `portable` installer type from the binaries.
|
||||||
|
"$komac_bin" update "$PKG" \
|
||||||
|
--version "$VER" \
|
||||||
|
--urls "$base/rdbms-playground-$TAG-x86_64-pc-windows-gnu.exe" \
|
||||||
|
"$base/rdbms-playground-$TAG-aarch64-pc-windows-gnullvm.exe" \
|
||||||
|
--submit
|
||||||
|
echo "winget: update PR submitted for $PKG $VER (Microsoft review is async)."
|
||||||
|
|
||||||
|
# No `needs:` between jobs — each is independent and idempotent, so one failing
|
||||||
|
# or being added never breaks another.
|
||||||
|
|||||||
@@ -238,3 +238,82 @@ needed for *browser-download* trust, not for the package-manager paths,
|
|||||||
which are all working now.
|
which are all working now.
|
||||||
|
|
||||||
**Remaining D3:** winget only.
|
**Remaining D3:** winget only.
|
||||||
|
|
||||||
|
## Amendment 4 — 2026-06-20: winget (D3 §3d) — the last package manager
|
||||||
|
|
||||||
|
winget wired as the fourth `publish.yaml` sibling job, **completing the D3
|
||||||
|
package-manager set** (crates.io/binstall, Scoop, Homebrew, winget; plus the
|
||||||
|
install scripts + direct binaries).
|
||||||
|
|
||||||
|
**Model — fundamentally unlike Scoop/Homebrew.** winget has no
|
||||||
|
self-hosted-source equivalent: its default catalog is the central,
|
||||||
|
**human-gated** GitHub repo `microsoft/winget-pkgs`, and you get in by
|
||||||
|
**opening a PR** of manifests into it. So the job submits a PR (via
|
||||||
|
**komac**, pinned `2.16.0`, prebuilt glibc binary — the CI image has no
|
||||||
|
cargo) and **Microsoft's pipeline validates async** (schema, SHA256,
|
||||||
|
AV/SmartScreen scan, sandbox install) + moderator review. It is *not* live
|
||||||
|
on dispatch like the bucket/tap.
|
||||||
|
|
||||||
|
**PackageIdentifier `LazyEvaluation.RdbmsPlayground`** (publisher segment =
|
||||||
|
the publishing entity / `lazyeval` org / license holder — consistent with
|
||||||
|
everything else). Bare-exe → winget **`portable`** installer type; x64
|
||||||
|
(`-pc-windows-gnu`) + arm64 (`-pc-windows-gnullvm`), komac infers arch +
|
||||||
|
type from the binaries.
|
||||||
|
|
||||||
|
**Auth — a dedicated GitHub bot, classic token.** komac needs a **classic
|
||||||
|
`public_repo` PAT**; **fine-grained tokens cannot open the cross-fork PR**
|
||||||
|
(komac #310 — the PR is created on the *target* repo you don't administer,
|
||||||
|
which the fine-grained model can't express). A classic `public_repo` token
|
||||||
|
can't be scoped to one repo, so it lives on a **dedicated GitHub bot
|
||||||
|
account** (a leak can't reach other repos — the lazyeval-ci reasoning), as
|
||||||
|
the `WINGET_GITHUB_TOKEN` secret, referenced **only** in the `winget` job
|
||||||
|
(job-level scoping keeps it away from the crates.io / lazyeval tokens) and
|
||||||
|
passed to the API guards via a 0600 curl config file (never argv).
|
||||||
|
|
||||||
|
**Idempotency — stronger than the others need.** A re-submitted version
|
||||||
|
would open a *duplicate* PR, so before submitting the job guards on **both**
|
||||||
|
(1) already-merged versions (`contents` API on
|
||||||
|
`manifests/l/LazyEvaluation/RdbmsPlayground/<ver>`) and (2) an already-open
|
||||||
|
PR for the id+version (`search/issues`). Either → clean skip, so a repeated
|
||||||
|
`publish` dispatch is safe even mid-review.
|
||||||
|
|
||||||
|
**Signing:** none required to *submit* (only MSIX needs it; ours is
|
||||||
|
portable). The unsigned binary may earn an AV/SmartScreen **manual-review
|
||||||
|
label** on the first PR — usually clears; a nudge toward Trusted Signing
|
||||||
|
(the UK Ltd clears the 3-year-history bar) but not a blocker. Continues the
|
||||||
|
parked Developer-ID/notarization posture.
|
||||||
|
|
||||||
|
**One-time bootstrap (manual — NOT in CI, because `komac new` is
|
||||||
|
interactive).** Run once to create the package in winget-pkgs, then CI
|
||||||
|
`komac update` handles every release after:
|
||||||
|
|
||||||
|
```
|
||||||
|
komac token update # paste the bot's classic public_repo PAT at the prompt (not in argv/history)
|
||||||
|
komac new LazyEvaluation.RdbmsPlayground \
|
||||||
|
-v 0.2.0 \
|
||||||
|
-u https://git.lazyeval.net/oli/rdbms-playground/releases/download/v0.2.0/rdbms-playground-v0.2.0-x86_64-pc-windows-gnu.exe \
|
||||||
|
https://git.lazyeval.net/oli/rdbms-playground/releases/download/v0.2.0/rdbms-playground-v0.2.0-aarch64-pc-windows-gnullvm.exe \
|
||||||
|
--publisher "Lazy Evaluation Ltd" --package-name "RDBMS Playground" \
|
||||||
|
--moniker rdbms-playground --license "MIT OR Apache-2.0" \
|
||||||
|
--package-url https://relplay.org -s
|
||||||
|
```
|
||||||
|
komac downloads the two exes, detects the bare binary as **`portable`**, and
|
||||||
|
**prompts for the command alias — enter `rdbms-playground`** so users get
|
||||||
|
that on PATH (not the long versioned filename). `-s` opens the PR; fill any
|
||||||
|
remaining prompts. (Flags verified against komac 2.16.0: `-v/--version`,
|
||||||
|
`-u/--urls`, `-s/--submit`; the alias has no flag and is prompted.) Once
|
||||||
|
that first PR merges, the CI job's `komac update` takes over.
|
||||||
|
|
||||||
|
**Bootstrap learnings (2026-06-21, first real run).** GitHub bot account =
|
||||||
|
`lazyeval-ci`. Its fork **`lazyeval-ci/winget-pkgs` must exist *before*
|
||||||
|
`komac new`** — komac's auto-fork races on winget-pkgs (one of GitHub's
|
||||||
|
largest repos) and fails with *"Could not resolve to a Repository
|
||||||
|
'lazyeval-ci/winget-pkgs'"*; fork it manually (web **Fork**, or `gh repo
|
||||||
|
fork microsoft/winget-pkgs --clone=false`), wait for it to populate, then
|
||||||
|
re-run. **Alias:** for a **direct single-exe portable**, winget derives the
|
||||||
|
on-PATH command from **`Commands[0]`** (the interactive "Commands" prompt →
|
||||||
|
`rdbms-playground`) — **not** `PortableCommandAlias`, which is
|
||||||
|
**archive/nested-portable-only** and must NOT be added to our manifest
|
||||||
|
(verified against winget-cli's portable-install logic + the 1.6.0 installer
|
||||||
|
schema; komac correctly omits it). So komac's generated manifest needs no
|
||||||
|
hand-editing. First PR: **#391335**.
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -96,9 +96,13 @@ since ADR-0027.)
|
|||||||
a **server-side Caddy rule** answering HEAD on release-download paths
|
a **server-side Caddy rule** answering HEAD on release-download paths
|
||||||
directly (Gitea's presigned-S3 redirect is method-bound and brew
|
directly (Gitea's presigned-S3 redirect is method-bound and brew
|
||||||
reuses a HEAD-signed URL for its GET) — see ADR-0056 Amendment 3;
|
reuses a HEAD-signed URL for its GET) — see ADR-0056 Amendment 3;
|
||||||
that rule lives in the Gitea edge config, not this repo. **Remaining:
|
that rule lives in the Gitea edge config, not this repo. **winget
|
||||||
winget** (komac on Linux CI, or a manual PR). Asset naming
|
wired** (ADR-0056 Amendment 4) — a `publish.yaml` `winget` job submits
|
||||||
`rdbms-playground-<tag>-<target>` is binstall-friendly.)*
|
a PR to `microsoft/winget-pkgs` via komac (`LazyEvaluation.RdbmsPlayground`,
|
||||||
|
portable, x64+arm64), guarded against duplicate PRs; pending the
|
||||||
|
**one-time manual `komac new` bootstrap** + Microsoft review before
|
||||||
|
it goes live. Asset naming `rdbms-playground-<tag>-<target>` is
|
||||||
|
binstall-friendly.)*
|
||||||
|
|
||||||
## TUI shell
|
## TUI shell
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user