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
21 changes: 15 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,19 @@ jobs:
.claude-plugin/plugin.json .codex-plugin/plugin.json
if git diff --quiet -- .claude-plugin/plugin.json .codex-plugin/plugin.json; then
echo "plugin manifests already at $RELEASE_VERSION; nothing to commit"
exit 0
else
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "release: stamp plugin manifests to $RELEASE_VERSION" \
-- .claude-plugin/plugin.json .codex-plugin/plugin.json
git push origin main
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "release: stamp plugin manifests to $RELEASE_VERSION" \
-- .claude-plugin/plugin.json .codex-plugin/plugin.json
git push origin main
# Advance the stable channel ref to the stamped release commit. The
# spacedock-dev/marketplace stable entry pins source.ref=stable (a moving
# branch, not a per-release tag), so a fresh `spacedock@spacedock` install
# resolves whatever this branch points at — this push is what publishes the
# release to the stable channel, replacing a hand-edit of the marketplace
# repo. main HEAD is the stamped release commit and stable is a prior
# release commit, so this fast-forwards. Same-repo push, so the default
# GITHUB_TOKEN suffices (no cross-repo PAT, unlike the homebrew-tap push).
git push origin main:refs/heads/stable
37 changes: 35 additions & 2 deletions internal/release/channel_agreement_guard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ const stableChannelBranch = "main"
// It reads the step's run block, finds the `git switch <branch>` and `git push
// origin <branch>` commands, and returns the branch only when BOTH name the same
// branch (a switch/push split would itself be a drift). Returns "" when the step,
// or either command, is absent.
// or either command, is absent. The step also pushes the stable channel ref
// (`git push origin main:refs/heads/stable`); that refspec push (target contains
// `:`) is skipped here so it does not shadow the bare-branch stamp target.
func releaseStampTarget(workflow string) string {
for _, step := range parseWorkflowSteps(workflow) {
if step.name != "Stamp plugin manifests to the release version" {
Expand All @@ -42,7 +44,7 @@ func releaseStampTarget(workflow string) string {
if len(fields) == 3 && fields[0] == "git" && fields[1] == "switch" {
switchTo = fields[2]
}
if len(fields) == 4 && fields[0] == "git" && fields[1] == "push" && fields[2] == "origin" {
if len(fields) == 4 && fields[0] == "git" && fields[1] == "push" && fields[2] == "origin" && !strings.Contains(fields[3], ":") {
pushTo = fields[3]
}
}
Expand Down Expand Up @@ -150,6 +152,37 @@ func TestStableChannelBinaryPairAgreesOnMain(t *testing.T) {
}
}

// stampStepAdvancesStableRef reports whether the "Stamp plugin manifests" step
// pushes the stamped commit to the stable channel ref. It looks for a
// `git push origin <src>:refs/heads/stable` command in the step's run block.
func stampStepAdvancesStableRef(workflow string) bool {
for _, step := range parseWorkflowSteps(workflow) {
if step.name != "Stamp plugin manifests to the release version" {
continue
}
for _, command := range executableShellCommands(step.run) {
fields := strings.Fields(command)
if len(fields) == 4 && fields[0] == "git" && fields[1] == "push" && fields[2] == "origin" && strings.HasSuffix(fields[3], ":refs/heads/stable") {
return true
}
}
}
return false
}

// TestStampStepAdvancesStableRef locks the stable-channel publish mechanism: the
// release stamp step MUST push the release commit to the `stable` ref, because the
// spacedock-dev/marketplace stable entry pins source.ref=stable. Without this push
// the stable channel would freeze at the prior release forever (a fresh
// `spacedock@spacedock` install would resolve the old commit), since the marketplace
// manifest is intentionally static and no longer hand-edited per release. The
// command is parsed out of the real release.yml, so dropping the push reds this.
func TestStampStepAdvancesStableRef(t *testing.T) {
if !stampStepAdvancesStableRef(readReleaseWorkflow(t)) {
t.Error("release.yml stamp step does not push to refs/heads/stable; the stable marketplace channel (source.ref=stable) would never advance past the prior release")
}
}

// TestEdgeChannelStampsNext locks the channel-separation half: the edge build
// must keep stamping `next` even as the stable build moves to `main`, so the two
// channels resolve distinct plugin sources rather than collapsing to one branch.
Expand Down
Loading