Repository navigation
feat(packaging): install Entracte with Scoop on Windows (#359) - #361
Merged
Merged
Conversation
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>
Contributor
🔍 Advisory audit reportThese checks don't block merges — they surface drift in dependencies, licensing, and external links. cargo-deny
|
Codecov Report✅ All modified and coverable lines are covered by tests. 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
🚀 New features to boost your workflow:
|
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>
Contributor
📖 Docs preview✅ Built and deployed for commit
Posted by docs-preview.yml — updates in place on every push. |
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>
Owner
Author
|
Two CI notes for the record:
|
This was referenced Oct 2, 2026
…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
# Conflicts: # CHANGELOG.md
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #359 — Entracte can be installed with Scoop on Windows:
Where the bucket lives: a
bucket/directory in this repo. A Scoop bucket is just a repository containingbucket/*.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.rbalready lives in this repo; Homebrew requires a separate repo for a tap, Scoop does not. The nameentracteis free in bothScoopInstaller/MainandScoopInstaller/Extras(checked at implementation time, as #359 asked), so nocairn-timetracker-style suffix is needed.What changed
scripts/scoop-manifest.mjs— generatesbucket/entracte.json:$schema,version,url,hash,bin,shortcuts,notes,checkver,autoupdate.binis[["Entracte.exe", "entracte"]]so the shim is lowercaseentracte, matching the cask'sbinaryline.autoupdate's hash points at the release'sSHA256SUMS.txtso Scoop's updater does not download the archive just to digest it.release.yml— buildsEntracte_<version>_x64-portable.zipinbuild-windows-unsigned(stagestarget/release/entracte.exeasEntracte.exebeside the LICENSE) and ships it with the rest of the Windows bundle, which also puts it inSHA256SUMS.txt. Scoop extracts archives rather than running installers, and the NSIS setup's own location would fight Scoop's~/scoop/appslayout. The zip rides as its own artifact rather than insidewindows-unsigned, which is submitted to SignPath — whose policy covers the two bundles, not an archive..github/workflows/bump-scoop.yml— regenerates the manifest onrelease: publishedand commits it straight tomain, shaped afterbump-cask.yml: no PR (Actions cannot create one here —can_approve_pull_request_reviewsis 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: 0explicitly (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 finalif: 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'sSHA256SUMS.txtas a cross-check that has to agree.checkverreadsreleases/latest(GitHub excludes prereleases from it), the generator rejects a prerelease version, and the workflow refuses a prerelease tag. Ascoopinstall cannot see the in-app update-channel setting, so it would have no way to opt out of betas.docs/guide/install.md, a "Scoop bucket" section indocs/developer/releases.md,bucket/README.md, the README install block, the portable zip added to the install page's download picker (download-detect.tscurates that list, so a new artifact matching no rule is invisible there), and a CHANGELOG entry.bucket/entracte.jsonis 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
git addthengit diff --cached --quiet) —git diffreports no change for a file git has never tracked, which would have made the very first release a silent no-op.checkver's version regex and the generator's validation are the same constant (VERSION), so they cannot disagree about what a version looks like.assetName) called with${version}for the two workflows and$versionforautoupdate;scoop-manifest.test.tsasserts the literal appears in both workflow files.Review round (second commit)
An adversarial review pass found five things worth fixing, all addressed in
81316ac:windows-unsignedupload, so an error in it failedbuild-windows-unsigned→ skippedsign-windowsvianeeds:→ a release with no.msi, no.exe, no.sig. Both Scoop steps now run last in that job and arecontinue-on-error; so is the artifact download insign-windows, and the copy tolerates a missing zip with a warning.bump-scoop.yml's missing-checksum guard is the alarm.pwshstep before a real release (audit:workflow-shellparses bash only).build-preview.ymlnow packages the same zip from the debug build on every PR and assertsEntracte.exesits at the archive root — the same class of bug as the AppImage icon this repo was already bitten by.windows_subsystem = "windows", so it never attaches to the calling console:entracte pause 30mlands over IPC, buthelp,statusand every error message print into a closed handle. PuttingentracteonPATHis the first thing that made this reachable (neither installer does). The proper fix is anAttachConsoleshim 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, indocs/guide/cli.md, the install guide, the CHANGELOG and the manifest's ownnotes.gh issuehad 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_REPOadded here and inbump-cask.yml, which has the identical latent bug.bucket/rather than ENOENT-ing if the README ever moves;$schema; theautoupdatehash; the tag piped viaenvin 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.ymlalready ignoresscripts/**,docs/**andsrc/**/*.test.*, so this patch has no coverable lines; the generator is nonetheless fully exercised bysrc/test-fixtures/scoop-manifest.test.ts(18 cases), and the download-picker change bydocs/.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) coversscripts/scoop-manifest.mjs:assetName()pinned identically acrossrelease.yml,bump-scoop.ymland the generator (the drift this PR is most likely to grow) and against the docs download picker's regex; thebinshim name;autoupdate's URL template;checkveragreeing with the prerelease validator; andrenderManifest's 4-space, newline-terminated output. It also coversbump-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-cnotcontainsguards, and that this file runs from a workflow with nopaths-ignore— or it pins nothing.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.tsextended for the new portable-zip rule.Second review round (pre-merge trio)
critical-code-reviewer,security-reviewandsimplify, in that order. Neither review found anything Blocking. Four Required items and the suggestions worth taking landed in the latest commit:nullglobleaked out of the portable-zip block insign-windowsand intocp unsigned/*.msi unsigned/*.exe signed/.shoptis process-global, so with it on a missing.msisilently drops out of the argument list and the release ships with only the.exe— the class of failure the zip'scontinue-on-errorexists to prevent. Nowcompgen -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.)ci.yml'spaths-ignorecovers.github/workflows/release.ymlanddocs/**, andpaths-ignoreskips 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 fromaudit.yml, which carries no path filter by design.release.ymlstays ignored, so this adds one step to an already-running ubuntu job rather than a 3-OS matrix.0.0.14, which the generator turns into av0.0.14download URL. Tightened tovMAJOR.MINOR.PATCH[-prerelease], admitting prereleases only so the next guard can reject them with a message that explains why.steps.meta.outputs.tag, which the tag validation fails before publishing. It now reads the event, like theconcurrencygroup already did.Taken from the suggestions: the manifest hash now comes from the downloaded bytes with
SHA256SUMS.txtas a cross-check (that hash is the only integrity check ascoop installperforms, and the sums file is composed once over whatever was on the draft, so a later--clobberor a partialchecksumsre-run could otherwise publish a stale one); a missing zip is named explicitly, becausegh release downloadexits 0 as long as one of its two patterns matched; downloads land inRUNNER_TEMPrather than the checkout the next step commits from;NOTICEships in the zip (Apache-2.0 4(d), and an extracted archive has nowhere else to carry it); andbuild-preview.yml's-notcontainsbecame-cnotcontains, since PowerShell's default string comparison is case-insensitive and would have accepted anentracte.exethat was never renamed — the one bug that step exists to catch.bump-cask.ymlcarried 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.simplifymergedbump-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$schemaregex against a literal in the same module, aJSON.parse(JSON.stringify(x))round-trip, and a$versionround-trip through the helpers that produced it).Deliberately not done here, as follow-ups:
bump-cask.ymlandbump-scoop.ymlare one mechanism copied twice and want aworkflow_callworkflow; thepwshstaging is duplicated betweenrelease.ymlandbuild-preview.yml, so the smoke test checks a transcription rather than the real step; and the asset name could be derived fromassetName()in both workflows instead of pinned by a test. Each is a larger change than a packaging PR should carry, and the last two touchpwshthat cannot be exercised outside a Windows runner.Checked by hand, because no gate covers it
ScoopInstaller/Scoop'sschema.json. Nothing in CI validates against that schema.build-previewworkflow deliberately to exercise the newpwshpackaging step on a realwindows-latestrunner — "Verify the portable zip can be packaged" passed twice (runs 36894069516, 36894026697), assertingEntracte.exesits at the archive root.audit:workflow-shellparses 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:
v0.0.14-dryrun.1) — or dispatchrelease.ymlagainst it withallow_version_mismatch=true— and let it build a hidden draft release (releaseDraft: true).Entracte_0.0.14-dryrun.1_x64-portable.zip, that it appears inSHA256SUMS.txt, and that the zip containsEntracte.exe+LICENSE.bump-scoop.ymlgates onrelease: published, exactly likebump-cask.yml, so the draft touches no manifest.bump-scoop.ymlruns for the first time and commitsbucket/entracte.json. If anything goes wrong it files an issue against itself;workflow_dispatchtakes a tag for a manual retry. Thenscoop bucket add entracte https://github.com/drmowinckels/entracte && scoop install entracte/entracteon a Windows box is the end-to-end check — worth also runningscoop checkver entractethere to confirmcheckver/autoupdateresolve againstreleases/latest.docs/guide/install.mdandREADME.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