Skip to content

feat(packaging): install Entracte with Scoop on Windows (#359) - #361

Merged
drmowinckels merged 8 commits into
mainfrom
feat/scoop-packaging
Oct 5, 2026
Merged

drmowinckels merged 8 commits into
mainfrom
feat/scoop-packaging

Conversation

@drmowinckels

@drmowinckels drmowinckels commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #359 — Entracte can be installed with Scoop on Windows:

scoop bucket add entracte https://github.com/drmowinckels/entracte
scoop install entracte/entracte

Where the bucket lives: a bucket/ directory in this repo. A Scoop bucket is just a repository containing bucket/*.json, so a second repo buys nothing and costs a cross-repo PAT for CI to push with. It is also the closer parity with how the cask is handled here — Casks/entracte.rb already lives in this repo; Homebrew requires a separate repo for a tap, Scoop does not. The name entracte is free in both ScoopInstaller/Main and ScoopInstaller/Extras (checked at implementation time, as #359 asked), so no cairn-timetracker-style suffix is needed.

What changed

  • scripts/scoop-manifest.mjs — generates bucket/entracte.json: $schema, version, url, hash, bin, shortcuts, notes, checkver, autoupdate. bin is [["Entracte.exe", "entracte"]] so the shim is lowercase entracte, matching the cask's binary line. autoupdate's hash points at the release's SHA256SUMS.txt so Scoop's updater does not download the archive just to digest it.
  • release.yml — builds Entracte_<version>_x64-portable.zip in build-windows-unsigned (stages target/release/entracte.exe as Entracte.exe beside the LICENSE) and ships it with the rest of the Windows bundle, which also puts it in SHA256SUMS.txt. Scoop extracts archives rather than running installers, and the NSIS setup's own location would fight Scoop's ~/scoop/apps layout. The zip rides as its own artifact rather than inside windows-unsigned, which is submitted to SignPath — whose policy covers the two bundles, not an archive.
  • .github/workflows/bump-scoop.yml — regenerates the manifest on release: published and commits it straight to main, shaped after bump-cask.yml: no PR (Actions cannot create one here — can_approve_pull_request_reviews is off, the cause of bump-cask.yml has failed on every release since v0.0.2 — cask silently strands behind #349), ref: main + fetch-depth: 0 explicitly (a release-triggered checkout defaults to the tag, which is behind main by everything merged since), rebase-and-retry on a lost push race, and a final if: failure() step that files or comments on an issue because nothing downstream would otherwise notice a red run. The hash is taken from the downloaded zip itself, with the release's SHA256SUMS.txt as a cross-check that has to agree.
  • Stable releases only, like the cask: checkver reads releases/latest (GitHub excludes prereleases from it), the generator rejects a prerelease version, and the workflow refuses a prerelease tag. A scoop install cannot see the in-app update-channel setting, so it would have no way to opt out of betas.
  • Docs — a Scoop section in docs/guide/install.md, a "Scoop bucket" section in docs/developer/releases.md, bucket/README.md, the README install block, the portable zip added to the install page's download picker (download-detect.ts curates that list, so a new artifact matching no rule is invisible there), and a CHANGELOG entry.

bucket/entracte.json is deliberately absent: it appears after the first release that ships a portable zip. Committing it now would advertise a download that 404s — so the install instructions say so rather than publishing a command that reports no manifest the day this merges.

The three bugs #359 said not to re-introduce

  1. The commit step stages before asking whether anything changed (git add then git diff --cached --quiet) — git diff reports no change for a file git has never tracked, which would have made the very first release a silent no-op.
  2. checkver's version regex and the generator's validation are the same constant (VERSION), so they cannot disagree about what a version looks like.
  3. The asset name is one function (assetName) called with ${version} for the two workflows and $version for autoupdate; scoop-manifest.test.ts asserts the literal appears in both workflow files.

Review round (second commit)

An adversarial review pass found five things worth fixing, all addressed in 81316ac:

  • A zip failure would have sunk signed Windows distribution. The packaging step sat before the windows-unsigned upload, so an error in it failed build-windows-unsigned → skipped sign-windows via needs: → a release with no .msi, no .exe, no .sig. Both Scoop steps now run last in that job and are continue-on-error; so is the artifact download in sign-windows, and the copy tolerates a missing zip with a warning. bump-scoop.yml's missing-checksum guard is the alarm.
  • Nothing exercised the pwsh step before a real release (audit:workflow-shell parses bash only). build-preview.yml now packages the same zip from the debug build on every PR and asserts Entracte.exe sits at the archive root — the same class of bug as the AppImage icon this repo was already bitten by.
  • The Windows CLI claim was false. The release binary is windows_subsystem = "windows", so it never attaches to the calling console: entracte pause 30m lands over IPC, but help, status and every error message print into a closed handle. Putting entracte on PATH is the first thing that made this reachable (neither installer does). The proper fix is an AttachConsole shim plus the pure-function test the coverage rule asks for — out of scope for a packaging PR, so it is tracked in Windows CLI prints nothing: the GUI-subsystem binary never attaches to the console #364 and the docs now describe what actually happens, in docs/guide/cli.md, the install guide, the CHANGELOG and the manifest's own notes.
  • The failure reporter could not file its issue: gh issue had no base repository, because the only step that can fail before the checkout is the tag validation, and at that point there is no git remote to infer one from. GH_REPO added here and in bump-cask.yml, which has the identical latent bug.
  • Smaller: the generator creates bucket/ rather than ENOENT-ing if the README ever moves; $schema; the autoupdate hash; the tag piped via env in the download step like its sibling; the "no SmartScreen prompt" claim softened to what is actually guaranteed.

Coverage

Workflows, a generated manifest, a generator script and its tests. codecov.yml already ignores scripts/**, docs/** and src/**/*.test.*, so this patch has no coverable lines; the generator is nonetheless fully exercised by src/test-fixtures/scoop-manifest.test.ts (18 cases), and the download-picker change by docs/.vitepress/theme/components/download-detect.test.ts. No Rust or frontend runtime code is touched.

Tests added, against the symbols they cover

  • src/test-fixtures/scoop-manifest.test.ts (18 cases) covers scripts/scoop-manifest.mjs: assetName() pinned identically across release.yml, bump-scoop.yml and the generator (the drift this PR is most likely to grow) and against the docs download picker's regex; the bin shim name; autoupdate's URL template; checkver agreeing with the prerelease validator; and renderManifest's 4-space, newline-terminated output. It also covers bump-scoop.yml's tag-shape pattern (lifted out of the workflow and exercised, not compared as text), its hash-from-bytes flow and cross-check, its failure reporter deriving the tag from the event, build-preview.yml's two -cnotcontains guards, and that this file runs from a workflow with no paths-ignore — or it pins nothing.
  • Also asserts the shape of bump-scoop.yml — no PR-creating step, ref: main, stage-before-diff — so the three bugs Scoop packaging for Windows #359 said not to re-introduce fail a test rather than a release.
  • docs/.vitepress/theme/components/download-detect.test.ts extended for the new portable-zip rule.

Second review round (pre-merge trio)

critical-code-reviewer, security-review and simplify, in that order. Neither review found anything Blocking. Four Required items and the suggestions worth taking landed in the latest commit:

  • nullglob leaked out of the portable-zip block in sign-windows and into cp unsigned/*.msi unsigned/*.exe signed/. shopt is process-global, so with it on a missing .msi silently drops out of the argument list and the release ships with only the .exe — the class of failure the zip's continue-on-error exists to prevent. Now compgen -G, which touches no global flag. (Both reviews found this independently; it was the only finding that made the signed path less safe than before the PR.)
  • The cross-file asset-name pin never ran for the file most likely to drift. ci.yml's paths-ignore covers .github/workflows/release.yml and docs/**, and paths-ignore skips the whole workflow when every changed file is ignored — so a rename in either sailed past the one test that catches it. The test now also runs from audit.yml, which carries no path filter by design. release.yml stays ignored, so this adds one step to an already-running ubuntu job rather than a 3-OS matrix.
  • The tag check validated characters, not shape. It accepted 0.0.14, which the generator turns into a v0.0.14 download URL. Tightened to vMAJOR.MINOR.PATCH[-prerelease], admitting prereleases only so the next guard can reject them with a message that explains why.
  • A rejected tag filed "bump-scoop failed for unknown". The reporter read steps.meta.outputs.tag, which the tag validation fails before publishing. It now reads the event, like the concurrency group already did.

Taken from the suggestions: the manifest hash now comes from the downloaded bytes with SHA256SUMS.txt as a cross-check (that hash is the only integrity check a scoop install performs, and the sums file is composed once over whatever was on the draft, so a later --clobber or a partial checksums re-run could otherwise publish a stale one); a missing zip is named explicitly, because gh release download exits 0 as long as one of its two patterns matched; downloads land in RUNNER_TEMP rather than the checkout the next step commits from; NOTICE ships in the zip (Apache-2.0 4(d), and an extracted archive has nowhere else to carry it); and build-preview.yml's -notcontains became -cnotcontains, since PowerShell's default string comparison is case-insensitive and would have accepted an entracte.exe that was never renamed — the one bug that step exists to catch.

bump-cask.yml carried two of the same latent bugs and got both fixes: the reporter's tag, and a depth-1 checkout under the same rebase-and-retry push loop, which has no merge base to rebase onto.

simplify merged bump-scoop.yml's download and regenerate steps so the asset name is spelled once per file, converted the structural test assertions from text grepping to parsing the YAML, and dropped three assertions that could not fail (a $schema regex against a literal in the same module, a JSON.parse(JSON.stringify(x)) round-trip, and a $version round-trip through the helpers that produced it).

Deliberately not done here, as follow-ups: bump-cask.yml and bump-scoop.yml are one mechanism copied twice and want a workflow_call workflow; the pwsh staging is duplicated between release.yml and build-preview.yml, so the smoke test checks a transcription rather than the real step; and the asset name could be derived from assetName() in both workflows instead of pinned by a test. Each is a larger change than a packaging PR should carry, and the last two touch pwsh that cannot be exercised outside a Windows runner.

Checked by hand, because no gate covers it

  • The generated manifest was parsed back as JSON and walked field-by-field against ScoopInstaller/Scoop's schema.json. Nothing in CI validates against that schema.
  • I ran the opt-in build-preview workflow deliberately to exercise the new pwsh packaging step on a real windows-latest runner — "Verify the portable zip can be packaged" passed twice (runs 36894069516, 36894026697), asserting Entracte.exe sits at the archive root. audit:workflow-shell parses bash only, so that step would otherwise have had no checking at all. The preview label has since been removed.

What the maintainer should verify before this ships (not run here — it creates outward-facing refs)

The usual safe release dry-run:

  1. Push a throwaway tag (v0.0.14-dryrun.1) — or dispatch release.yml against it with allow_version_mismatch=true — and let it build a hidden draft release (releaseDraft: true).
  2. Confirm the draft carries Entracte_0.0.14-dryrun.1_x64-portable.zip, that it appears in SHA256SUMS.txt, and that the zip contains Entracte.exe + LICENSE.
  3. Nothing outward-facing fires: bump-scoop.yml gates on release: published, exactly like bump-cask.yml, so the draft touches no manifest.
  4. Delete the tag and the draft.
  5. On the next stable release, bump-scoop.yml runs for the first time and commits bucket/entracte.json. If anything goes wrong it files an issue against itself; workflow_dispatch takes a tag for a manual retry. Then scoop bucket add entracte https://github.com/drmowinckels/entracte && scoop install entracte/entracte on a Windows box is the end-to-end check — worth also running scoop checkver entracte there to confirm checkver/autoupdate resolve against releases/latest.
  6. Once that manifest lands, drop the "arrives with the next stable release" warnings from docs/guide/install.md and README.md.

Note that a beta release will not bump the bucket (by design), so if the next few published releases are weekly betas the manifest waits for the next stable one.

Closes #359

🤖 Generated with Claude Code

Scoop reads manifests from a repository's `bucket/` directory, so the bucket
lives here rather than in a separate `scoop-entracte` repo — one repo, no
cross-repo PAT for CI to push with, and the same arrangement `Casks/` already
uses. (Homebrew *requires* a separate repo for a tap; Scoop does not, so this
is the closer parity, not a departure.)

`release.yml` now also builds `Entracte_<version>_x64-portable.zip`: Scoop
extracts archives rather than running installers, and the NSIS setup's own
install location would fight Scoop's `~/scoop/apps` layout. The app is a single
self-contained exe, so staging `target/release/entracte.exe` as `Entracte.exe`
beside the LICENSE and compressing it is the whole job. It rides as its own
artifact rather than inside `windows-unsigned`, which goes to SignPath — whose
policy covers the two bundles, not an archive.

`bump-scoop.yml` regenerates the manifest on `release: published` and commits
it straight to `main`, shaped after `bump-cask.yml` for the same reasons:
Actions cannot open PRs here, a release-triggered checkout defaults to the tag
rather than `main`, and nothing downstream notices a red run, so it files an
issue against itself. The hash comes from the release's own `SHA256SUMS.txt`.

Stable releases only, like the cask: `checkver` reads `releases/latest` (which
excludes prereleases), the generator rejects a prerelease version, and the
workflow refuses a prerelease tag. A `scoop` install cannot see the in-app
update-channel setting, so it would have no way to opt out.

The three bugs the Cairn review caught are avoided rather than re-introduced:
the commit step stages before asking whether anything changed (`git diff` says
nothing changed about a file it has never tracked, which would have made the
first release a silent no-op); `checkver`'s regex is the same constant the
generator validates against; and the asset name is a single function pinned
across all three files by `scoop-manifest.test.ts`.

`bucket/entracte.json` is deliberately absent until the first release that
ships a portable zip — committing it now would advertise a 404.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🔍 Advisory audit report

These checks don't block merges — they surface drift in dependencies, licensing, and external links.

cargo-deny

⚠️ findings
�[0m�[1m�[38;5;9merror[vulnerability]�[0m�[1m: Wasmtime component async-lifted callback result count is unvalidated, causing a native stack buffer overflow�[0m
    �[0m�[36m┌─�[0m /home/runner/work/entracte/entracte/src-tauri/Cargo.lock:560:1
    �[0m�[36m│�[0m
�[0m�[36m560�[0m �[0m�[36m│�[0m �[0m�[31mwasmtime 43.0.2 registry+https://github.com/rust-lang/crates.io-index�[0m
    �[0m�[36m│�[0m �[0m�[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[0m�[31msecurity vulnerability detected�[0m
    �[0m�[36m│�[0m
    �[0m�[36m├�[0m ID: RUSTSEC-2026-0327
    �[0m�[36m├�[0m Advisory: https://rustsec.org/advisories/RUSTSEC-2026-0327
    �[0m�[36m├�[0m This is an entry in the RustSec database for the Wasmtime security advisory
      located at
      https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-32h6-97mm-8q3c
      For more information see the GitHub-hosted security advisory.
    �[0m�[36m├�[0m Announcement: https://github.com/bytecodealliance/wasmtime/pull/14471
    �[0m�[36m├�[0m Solution: Upgrade to >=48.0.4, <49.0.0 OR >=49.0.2 (try `cargo update -p wasmtime`)
    �[0m�[36m├�[0m wasmtime v43.0.2
      ├── extism v1.30.0
      │   └── entracte v0.0.13
      ├── wasi-common v43.0.2
      │   └── extism v1.30.0 (*)
      └── wiggle v43.0.2
          ├── extism v1.30.0 (*)
          └── wasi-common v43.0.2 (*)

advisories �[31mFAILED�[0m, bans �[32mok�[0m, licenses �[32mok�[0m, sources �[32mok�[0m

lychee (broken links)

✅ all links resolve

npm audit

⚠️ findings
# npm audit report

braces  *
Severity: high
braces vulnerable to stack-exhaustion denial of service through deeply nested patterns - https://github.com/advisories/GHSA-vfj7-8cjw-p6xm
fix available via `npm audit fix --force`
Will install stylelint@7.7.0, which is a breaking change
node_modules/braces
  micromatch  >=0.2.0
  Depends on vulnerable versions of braces
  node_modules/micromatch
    fast-glob  *
    Depends on vulnerable versions of micromatch
    node_modules/fast-glob
      globby  >=8.0.0
      Depends on vulnerable versions of fast-glob
      node_modules/globby
        stylelint  >=7.7.1
        Depends on vulnerable versions of fast-glob
        Depends on vulnerable versions of globby
        Depends on vulnerable versions of micromatch
        node_modules/stylelint
          stylelint-config-recommended  *
          Depends on vulnerable versions of stylelint
          node_modules/stylelint-config-recommended
            stylelint-config-standard  >=16.0.0
            Depends on vulnerable versions of stylelint
            Depends on vulnerable versions of stylelint-config-recommended
            node_modules/stylelint-config-standard

7 high severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

@codecov

codecov Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.72%. Comparing base (d8aa8ab) to head (dfc3951).

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #361    +/-   ##
========================================
  Coverage   89.72%   89.72%            
========================================
  Files         147      148     +1     
  Lines       25450    25671   +221     
  Branches      861      861            
========================================
+ Hits        22834    23033   +199     
- Misses       2590     2612    +22     
  Partials       26       26            
Flag Coverage Δ
frontend 90.39% <ø> (ø)
rust 89.64% <ø> (+<0.01%) ⬆️
Components Coverage Δ
Frontend (TypeScript) 90.39% <ø> (ø)
Backend (Rust) 89.64% <ø> (+<0.01%) ⬆️
see 7 files with indirect coverage changes
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The install page's "All platforms & formats" list is curated by
`download-detect.ts`'s RULES, not derived from the release's asset list, so a
new artifact that matches no rule is silently invisible there — while
install.md now names it. Ranked last for Windows: right for Scoop and for
anyone who wants no installer, but it does not bootstrap WebView2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

📖 Docs preview

✅ Built and deployed for commit dfc39516e49e0bea07f18b3c23bc82cd7b1fffe9.

Preview URL https://pr-361--entract.netlify.app
Build logs https://github.com/drmowinckels/entracte/actions/runs/37294625084

Posted by docs-preview.yml — updates in place on every push.

drmowinckels and others added 2 commits October 1, 2026 18:38
Review findings on the Scoop channel, in the order they would have bitten:

* **A zip failure would have sunk signed Windows distribution.** The packaging
  step sat before the `windows-unsigned` upload, so any error in it failed
  `build-windows-unsigned`, which skipped `sign-windows` via `needs:` and left
  the release with no `.msi`, no `.exe` and no `.sig`. Both Scoop steps now run
  last in the job and are `continue-on-error`, the artifact download in
  `sign-windows` likewise, and the copy tolerates a missing zip with a warning.
  `bump-scoop.yml`'s "did the release build the portable zip?" guard is the
  alarm — which is what it was written for.

* **Nothing exercised the pwsh step before a real release.** `audit:workflow-shell`
  parses bash only, so a wrong exe path or an empty archive would have surfaced
  at release time with the bundles already built — the AppImage-icon class of
  bug this repo has already been bitten by. `build-preview.yml` now packages the
  same zip from the debug build on every PR and asserts `Entracte.exe` is at its
  root.

* **The CLI claim was not true on Windows.** The release binary is
  `windows_subsystem = "windows"`, so it never attaches to the calling console:
  `entracte pause 30m` lands over IPC, but `help`, `status` and every error
  message print into a closed handle. Putting `entracte` on `PATH` is the first
  thing that made this reachable. Fixing it properly means an `AttachConsole`
  shim plus the pure-function test the coverage rule asks for — out of scope for
  a packaging PR, so it is tracked as #364 and the docs now say what actually
  happens instead of overselling it.

* **The failure reporter could not file its issue.** `gh issue` had no base
  repository: the only step that can fail before the checkout is the tag
  validation, and at that point there is no git remote to infer one from, so the
  loud-failure mechanism was itself silent. `GH_REPO` added here and in
  `bump-cask.yml`, which has the identical shape.

* `bucket/` is only tracked by virtue of its README, so the generator creates
  the directory rather than ENOENT-ing at release time if that ever moves.

* The install instructions now say the manifest arrives with the first stable
  release that ships the zip, rather than publishing commands that report no
  manifest the day this merges.

Also from review: `$schema` for editor/lint validation, an `autoupdate` hash
read from the release's `SHA256SUMS.txt` instead of re-downloading the archive
to digest it, the tag piped through `env` in the download step like its sibling,
and the SmartScreen wording softened to what is actually guaranteed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`build-preview.yml` is opt-in behind the `build:installers` label, not a per-PR
job, so calling the portable-zip check a per-PR guard oversold it. It is a
pre-release smoke test to run when the packaging step changes — which is how it
was exercised on this PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@drmowinckels drmowinckels added the build:installers Build preview .deb/.rpm/.msi/.dmg installers as PR artifacts label Oct 1, 2026
@drmowinckels

Copy link
Copy Markdown
Owner Author

Two CI notes for the record:

  • rust (macos-latest) failed once and was re-run. The failure was ipc::tests::transport::authorized_request_round_trips_through_unix_socket panicking at src/ipc.rs:913 (round_trip(...).expect("round trip")), 1215 passed / 1 failed. That is the client connecting before the echo-server thread has bound the socket — a pre-existing race in the test, unrelated to this PR, which touches no Rust. Worth fixing separately (retry the connect, or have the server signal readiness over a channel) rather than leaving it to re-flake.
  • The opt-in installer build was run deliberately, to exercise the new pwsh packaging step on a real windows-latest runner — audit:workflow-shell only parses bash, so nothing else would have: run 36894069516, where Verify the portable zip can be packaged is green on Windows. The first label-triggered run was cancelled by the concurrency group racing the push just before it; it has been re-run, which is why build (…) appears twice in the history.

drmowinckels and others added 3 commits October 2, 2026 17:01
…path

Adversarial review, security review and a simplification pass over #361.
Two findings were real enough to change behaviour.

`nullglob` leaked out of the portable-zip block in `sign-windows` and into
`cp unsigned/*.msi unsigned/*.exe signed/`. With it on, a missing `.msi`
silently vanishes from the argument list and the release ships with only the
`.exe` — the exact class of failure the zip's `continue-on-error` exists to
avoid. Replaced with `compgen -G`, so no process-global flag is touched.

The cross-file asset-name pin never ran for the file most likely to drift:
`ci.yml`'s `paths-ignore` covers `.github/workflows/release.yml` *and*
`docs/**`, and `paths-ignore` skips the whole workflow when every changed file
is ignored. A rename in either file therefore sailed past the one test that
catches it. The test now also runs from `audit.yml`, which carries no path
filter by design; `release.yml` stays ignored, so no CI minutes are added.

Also in `bump-scoop.yml`:

* The tag check validated characters, not shape, and accepted `0.0.14` — which
  the generator turns into a `v0.0.14` download URL. Tightened to
  `vMAJOR.MINOR.PATCH[-prerelease]`, admitting prereleases only so the next
  guard can reject them with a message that explains why.
* The manifest hash now comes from the downloaded bytes, with `SHA256SUMS.txt`
  as a cross-check that must agree. That hash is the only integrity check a
  `scoop install` performs, and the sums file is composed once over whatever
  was on the draft at that moment, so a later `--clobber` re-upload or a
  partial re-run of `checksums` could otherwise put a stale hash in the bucket.
* A missing zip is named explicitly: `gh release download` exits 0 as long as
  one of its two patterns matched.
* Download and regenerate merged into one step, so the asset name is spelled
  once per file; downloads land in `RUNNER_TEMP`, not the checkout the next
  step commits from.
* The failure reporter reads the tag from the event rather than
  `steps.meta.outputs.tag`, which the tag validation fails before publishing —
  so a rejected tag filed "bump-scoop failed for unknown".

`bump-cask.yml` had two of the same latent bugs: the reporter's tag, and a
depth-1 checkout under the same rebase-and-retry push loop, which has no merge
base to rebase onto.

The portable zip now carries `NOTICE` as well as `LICENSE`; Apache-2.0 4(d)
requires it in a redistribution and an extracted archive has nowhere else to
put it. `build-preview.yml`'s archive check used `-notcontains`, which is
case-insensitive in PowerShell and so would have accepted an `entracte.exe`
that was never renamed — the one bug that step exists to catch.

Tests, against the symbols they cover:

* `src/test-fixtures/scoop-manifest.test.ts` gains five cases over
  `bump-scoop.yml`, `build-preview.yml` and `audit.yml`: the tag-shape pattern
  is lifted out of the workflow and exercised (accepts `v0.0.14`, rejects
  `0.0.14`/`V0.0.14`, admits `v0.1.2-beta.1`); the hash-from-bytes flow and its
  cross-check; the reporter deriving the tag from the event; the pwsh
  `-cnotcontains` guards and the `NOTICE` copy; and that this file runs from a
  workflow with no `paths-ignore`, or it pins nothing.
* The structural claims (`release: published`, `ref: main`, `fetch-depth: 0`,
  the reporter's `env`) now parse the YAML instead of grepping it, so a reflow
  can neither pass nor fail them. Three assertions that could not fail were
  dropped or replaced: the `$schema` regex against a literal in the same
  module, the `JSON.parse(JSON.stringify(x))` round-trip (replaced by the
  4-space-and-trailing-newline formatting that a bucket commit actually cares
  about), and the `$version` round-trip through the helpers that produced it
  (replaced by the literal template Scoop substitutes into).
* `assetName` is pinned against the docs download picker's regex, which lives
  behind `docs/**` and so was previously unguarded from this side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	.github/audit/cspell/project-words.txt
drmowinckels added a commit that referenced this pull request Oct 5, 2026
#367 fixed `entracte help` / `status` printing into a closed handle on
Windows but shipped without a changelog entry, and #361's Scoop entry
had been carrying the caveat that those commands were still silent —
which that fix made untrue. Removing the stale caveat left the fix
undocumented, so record it on its own.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	.github/audit/cspell/project-words.txt
@drmowinckels
drmowinckels merged commit 236f4e7 into main Oct 5, 2026
13 checks passed
@drmowinckels
drmowinckels deleted the feat/scoop-packaging branch October 5, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scoop packaging for Windows

1 participant