Repository navigation
feat: distribute the CLI via Scoop - #691
NickJosevski wants to merge 3 commits into
Conversation
| New-Item -ItemType Directory -Path "bucket" -Force | Out-Null | ||
| Copy-Item -Path "$extractedPath/scoop/*" -Filter "*.json" -Destination "bucket" -Force | ||
|
|
||
| git diff-index --quiet HEAD || (git commit -a -m "Update for release $packageVersion" ` |
There was a problem hiding this comment.
The first release through this step can never publish the manifest: in a fresh clone the copied bucket/octopus-cli.json is a new untracked file, and git diff-index HEAD only compares tracked files — it exits 0 and the || branch is skipped, so nothing is committed or pushed and the step still goes green. Because every deploy starts from a fresh --depth 1 clone, this repeats on every release until someone commits the manifest to the bucket repo by hand. Even when the guard does fire, git commit -a also only stages tracked-file changes, so it would fail on an untracked manifest anyway ("nothing added to commit but untracked files present").
Suggested fix — stage first, then guard on the index:
git add bucket
git diff-index --quiet --cached HEAD || (git commit -m "Update for release $packageVersion" `
&& git push --repo $origin `
)There was a problem hiding this comment.
The diagnosis is right, but it no longer holds on the branch head — this was already fixed in 1174696 ("fix(release): stage the scoop manifest before testing for changes"), which is the exact git add bucket + --cached shape suggested here. Current code in .octopus/deployment_process.ocl:
git add bucket
if ($LASTEXITCODE -ne 0) { throw "Failed to stage the manifest" }
git diff-index --quiet --cached HEAD
if ($LASTEXITCODE -eq 0) {
Write-Host "Manifest already matches $packageVersion; nothing to publish."
Set-Location ..
exit 0
}I reproduced both halves of the claim against real git — scratch bare repo, clone, an untracked bucket/octopus-cli.json:
- The old guard
git diff-index --quiet HEADon the untracked manifest exits 0, so the||branch was skipped and nothing was committed or pushed. Finding confirmed. git commit -a -m ...on that same tree exits 1 withnothing added to commit but untracked files present. Confirmed — even a firing guard would have failed.- New path:
git add bucketexits 0, thengit diff-index --quiet --cached HEADexits 1, so the commit proceeds andgit log --name-onlyshowsbucket/octopus-cli.jsonin it. The first release now publishes. - Re-run with byte-identical content: staging then the
--cachedguard exits 0, so the idempotent early-exit still works and re-deploying the same version is a no-op rather than an empty commit.
One residual this shape does not close, which needs a decision. If no *.json ever lands in bucket — an empty scoop/ in the package, or a layout change that renames the extension — then git add bucket exits 0 having staged nothing, the --cached guard exits 0, and the step prints "Manifest already matches" and goes green having published nothing. I checked the empty-directory case against real git and both commands exit 0. The $ErrorActionPreference = "Stop" from c1b3aa7 catches a bad path (Copy-Item throws) but not a wildcard that legitimately matches zero files. The homebrew step has the identical gap, so adding an assertion here only would break the parity you asked for in the sibling thread.
Do you want a if (-not (Get-ChildItem bucket -Filter *.json)) { throw ... } style assertion after the copy — and if so, should the equivalent go into the homebrew step in this PR, or is that a follow-up?
To be clear about evidence: the git exit codes above are measured. I could not run the deployment process or any PowerShell — there is no pwsh on this worker — so the PowerShell-level behaviour is reasoned from the code, not executed.
| write-host "Using: packageVersion $packageVersion from $extractedPath" | ||
| } | ||
|
|
||
| git clone --depth 1 $origin octopus-scoop-bucket |
There was a problem hiding this comment.
Unlike the sibling homebrew step, this script sets no $ErrorActionPreference = "Stop" and never checks $LASTEXITCODE, so failures degrade into a silent green step instead of a clean error. Concretely: if Copy-Item fails (bad path, package layout change), the error is non-terminating, git diff-index then sees a clean tree and exits 0, and the deployment reports success having pushed nothing. A failed git clone cascades similarly — Set-Location fails non-terminating and the rest of the script runs in the wrong directory.
Suggest mirroring the homebrew step: $ErrorActionPreference = "Stop" at the top, plus if ($LASTEXITCODE -ne 0) { throw "..." } after the clone and push.
There was a problem hiding this comment.
This was already actioned in c1b3aa7 ("fix(release): fail the scoop publish step on errors instead of going green"), so the finding holds against 1a877a3 but not against the branch head. I diffed the scoop script line-by-line against the homebrew one; they now match:
$ErrorActionPreference = "Stop"immediately after theparam(...)block, same position as homebrew.if ($LASTEXITCODE -ne 0) { throw "Failed to clone $repo" }directly after the clone and beforeSet-Location, so the wrong-directory cascade you describe can no longer happen.$LASTEXITCODEchecks after staging, commit and push.- The guard block was tidied to match homebrew too: the unreachable
exitafter thethrowis gone, the emptiness test is[string]::IsNullOrWhiteSpace(...)rather than!$packageVersion, and theWrite-Hostmoved out of theelse.
The only remaining differences between the two scripts are ones the steps genuinely need to differ on: scoop commits straight to the default branch, so it has no git checkout -b and no check for one, and it has no pull-request block. Neither step checks $LASTEXITCODE after git config, so that is consistent as well.
Two things I want to be explicit about rather than claim as verified:
- I could not run this deployment process or any PowerShell — no
pwshon this worker — so the error-handling behaviour is reasoned from the code and from parity with the homebrew step that already runs in production. The git-level behaviour is the part I actually measured; see the sibling thread for the exit codes. $ErrorActionPreference = "Stop"closes the failingCopy-Itemcase but not the zero-match one.Copy-Item -Path "$extractedPath/scoop/*" -Filter "*.json"against a directory that exists and contains no matching file is not an error, so the silent-green path you are worried about survives in that one shape:git add bucketon an empty bucket exits 0 having staged nothing (measured), the--cachedguard then exits 0 (measured), and the step prints "Manifest already matches" and goes green having published nothing. The homebrew step has the identical gap, which is why I did not add a one-sided assertion here — I raised it as an open question on the other thread, since the fix should probably touch both steps.
On the copy path itself: .goreleaser.yaml emits the manifest under dist/scoop/, and .github/workflows/go-releaser.yml uploads dist/scoop/*.json alongside dist/**/*.rb, which makes dist/ the artifact root — so $extractedPath/scoop/*.json lines up the same way $extractedPath/homebrew/* does for homebrew. That is read off the config, not observed in a real package.
Serves Scoop from a bucket repo we own, OctopusDeploy/scoop-octopus, as the official ScoopInstaller/Main bucket only accepts widely-adopted tools. As with the homebrew tap, goreleaser writes the manifest and the Octopus deployment process commits it into the bucket. The manifest covers 64bit and arm64, making Scoop the only Windows package manager through which we ship a native ARM build. Closes the last unticked box on #37. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…green The scoop step set no $ErrorActionPreference and never checked $LASTEXITCODE, so a failed Copy-Item or git clone was non-terminating: the tree then looked clean, git diff-index exited 0 and the deployment reported success having published nothing. Mirror the homebrew step - stop on errors, throw on a failed clone, commit or push, and report the no-op case explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
In a fresh --depth 1 clone of an empty bucket repo the copied bucket/octopus-cli.json is untracked, and `git diff-index HEAD` only compares tracked files - it exits 0, the publish is skipped and the step still goes green, on every release until someone commits the manifest by hand. `git commit -a` would not have staged it either. Stage bucket/ first and guard on the index. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1174696 to
dbb0766
Compare
Part of #37 — Scoop was the last unticked box on the package manager list.
Change
Adds a
scoopsblock to.goreleaser.yamland a deployment process step that pushes the generated manifest.Scoop is served from a bucket repository we own, OctopusDeploy/scoop-octopus, rather than the official
ScoopInstaller/Mainbucket, which only accepts widely-adopted tools (500+ stars, 150+ forks). This mirrors the homebrew tap: goreleaser generates the manifest butskip_uploadstops it publishing, and the Octopus deployment process commits it intobucket/.Unlike the homebrew step, the scoop step commits straight to the default branch instead of raising a PR — a bucket is machine-maintained, there is nothing for a human to review.
The manifest covers both
64bitandarm64, so Scoop becomes the only Windows package manager through which we ship a native ARM build (winget and chocolatey are x64 only).Why draft
The bucket repository has to exist, and
Publish:Scoop:Username,Publish:Scoop:UserEmailandPublish:Scoop:ApiKeyhave to be set on the Octopus project, before this can be merged. Nothing here is verifiable end to end until a release runs.🤖 Generated with Claude Code