Skip to content

Commit 5128603

Browse files
authored
ci: recover alpha train GoReleaser state (#278)
## Summary - restore `.changeset` after the empty-changeset prune so GoReleaser does not see a dirty checkout - allow alpha release recovery when the matching `v<version>` tag already exists on a previous main commit but the Go release/image still need completion - check out an existing Go release tag before running GoReleaser, so the partial `v0.1.0-alpha.3` state can be completed from the tagged commit ## Validation - corepack pnpm nx test @zitadel/cli -- tests/unit/scripts/release-alpha-train.test.ts tests/unit/scripts/check-alpha-release-plan.test.ts - corepack pnpm run check -- --only release - node scripts/release-alpha-train.mjs status --published false --remote false - git diff --check ## Release notes / changeset - Added an empty changeset: `.changeset/fix-alpha-goreleaser-dirty-state.md` ## Notes - Run 27446357094 published all public npm packages at 0.1.0-alpha.3 and pushed `v0.1.0-alpha.3`, then GoReleaser failed because pruned empty changesets left the checkout dirty. - The current recovery probe from this branch reports `tag_exists=true`, `create_tag=false`, and `run_goreleaser=true` while HEAD is newer than `v0.1.0-alpha.3`.
1 parent 3589b00 commit 5128603

6 files changed

Lines changed: 89 additions & 49 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
---
3+

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,9 @@ jobs:
864864
# repository is public.
865865
NPM_CONFIG_PROVENANCE: "false"
866866

867+
- name: Restore changesets after publish decision
868+
run: git restore -- .changeset
869+
867870
- name: Inspect alpha release train candidate
868871
id: alpha-status
869872
env:
@@ -914,6 +917,12 @@ jobs:
914917
git tag -a "$TAG" -m "$TITLE"
915918
git push origin "$TAG"
916919
920+
- name: Check out existing Go release tag
921+
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true' }}
922+
env:
923+
TAG: ${{ steps.alpha.outputs.tag }}
924+
run: git checkout --detach "$TAG"
925+
917926
- name: Prune npm package tags for GoReleaser
918927
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
919928
run: |

apps/cli/tests/unit/scripts/check-alpha-release-plan.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,39 @@ describe("check-alpha-release-plan script", () => {
123123
).rejects.toThrow("must prune empty changesets before Changesets decides whether to publish");
124124
});
125125

126+
it("rejects a workflow that leaves pruned changesets dirty for GoReleaser", async () => {
127+
const { cwd, statusPath } = await fixtureRepo({
128+
ciWorkflow: validCiWorkflow().replace(
129+
[
130+
" - name: Restore changesets after publish decision",
131+
" run: git restore -- .changeset",
132+
"",
133+
].join("\n"),
134+
"",
135+
),
136+
});
137+
138+
await expect(
139+
checkAlphaReleasePlanModule.checkAlphaReleasePlan({ cwd, statusPath }),
140+
).rejects.toThrow("must restore pruned changesets before inspecting or running GoReleaser");
141+
});
142+
143+
it("rejects a workflow that cannot recover an existing Go release tag", async () => {
144+
const { cwd, statusPath } = await fixtureRepo({
145+
ciWorkflow: validCiWorkflow().replace(
146+
[
147+
" - if: ${{ steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true' }}",
148+
" run: git checkout --detach \"$TAG\"",
149+
].join("\n"),
150+
"",
151+
),
152+
});
153+
154+
await expect(
155+
checkAlphaReleasePlanModule.checkAlphaReleasePlan({ cwd, statusPath }),
156+
).rejects.toThrow("must check out an existing Go tag before recovering GoReleaser");
157+
});
158+
126159
it("rejects alpha release notes generated inside the checkout", async () => {
127160
const { cwd, statusPath } = await fixtureRepo({
128161
ciWorkflow: validCiWorkflow().replace(
@@ -264,7 +297,11 @@ function validCiWorkflow(): string {
264297
" uses: changesets/action@v1",
265298
" with:",
266299
" createGithubReleases: false",
267-
" - id: alpha-status",
300+
" - name: Restore changesets after publish decision",
301+
" run: git restore -- .changeset",
302+
"",
303+
" - name: Inspect alpha release train candidate",
304+
" id: alpha-status",
268305
" run: |",
269306
" node scripts/release-alpha-train.mjs status --published \"$PUBLISHED\" --remote false",
270307
" - if: ${{ steps.alpha-status.outputs.should_complete == 'true' }}",
@@ -275,6 +312,8 @@ function validCiWorkflow(): string {
275312
" cat \"$alpha_env\" >> \"$GITHUB_OUTPUT\"",
276313
" - if: ${{ steps.alpha.outputs.create_tag == 'true' }}",
277314
" run: git tag \"$TAG\"",
315+
" - if: ${{ steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true' }}",
316+
" run: git checkout --detach \"$TAG\"",
278317
" - if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}",
279318
" run: goreleaser release --clean",
280319
" - if: ${{ steps.alpha.outputs.update_release == 'true' }}",

apps/cli/tests/unit/scripts/release-alpha-train.test.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -121,31 +121,35 @@ describe("release-alpha-train script", () => {
121121
expect(result.shouldRunGoreleaser).toBe(true);
122122
});
123123

124-
it("rejects an existing Go release tag when it points at another commit during publish recovery", async () => {
124+
it("recovers an existing Go release tag when artifacts are missing", async () => {
125125
const cwd = await fixtureRepo();
126126

127-
await expect(
128-
releaseAlphaTrain.prepareAlphaReleaseTrain({
129-
cwd,
130-
execFile: commandMock({ tagCommit: "other-commit" }),
131-
published: true,
132-
}),
133-
).rejects.toThrow("release tag v0.1.0-alpha.5 already exists at other-commit");
127+
const result = await releaseAlphaTrain.prepareAlphaReleaseTrain({
128+
cwd,
129+
execFile: commandMock({ tagCommit: "other-commit" }),
130+
published: true,
131+
});
132+
133+
expect(result.shouldCreateTag).toBe(false);
134+
expect(result.shouldRunGoreleaser).toBe(true);
135+
expect(result.shouldUpdateRelease).toBe(true);
134136
});
135137

136-
it("skips normal main pushes when the current version was already released from another commit", async () => {
138+
it("skips GoReleaser when an existing tag from another commit is already complete", async () => {
137139
const cwd = await fixtureRepo();
138140

139-
await expect(
140-
releaseAlphaTrain.inspectAlphaReleaseTrain({
141-
cwd,
142-
execFile: commandMock({ tagCommit: "other-commit" }),
143-
remote: false,
141+
const result = await releaseAlphaTrain.prepareAlphaReleaseTrain({
142+
cwd,
143+
execFile: commandMock({
144+
tagCommit: "other-commit",
145+
releaseExists: true,
146+
imageExists: true,
144147
}),
145-
).resolves.toMatchObject({
146-
shouldComplete: false,
147-
skipReason: "version 0.1.0-alpha.5 was already released from other-commit",
148148
});
149+
150+
expect(result.shouldCreateTag).toBe(false);
151+
expect(result.shouldRunGoreleaser).toBe(false);
152+
expect(result.shouldUpdateRelease).toBe(true);
149153
});
150154

151155
it("ignores prerelease-tracked and empty changesets when recovering a versioned train", async () => {

scripts/check-alpha-release-plan.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,17 @@ export async function validateReleaseTooling(cwd, readFileFn = readFileDefault)
188188
].join("\n"),
189189
"release-alpha-train must prune empty changesets before Changesets decides whether to publish",
190190
);
191+
assertJobContains(
192+
ciWorkflow,
193+
"release-alpha-train",
194+
[
195+
" - name: Restore changesets after publish decision",
196+
" run: git restore -- .changeset",
197+
"",
198+
" - name: Inspect alpha release train candidate",
199+
].join("\n"),
200+
"release-alpha-train must restore pruned changesets before inspecting or running GoReleaser",
201+
);
191202
assertContains(
192203
ciWorkflow,
193204
"node scripts/release-alpha-train.mjs status --published \"$PUBLISHED\" --remote false",
@@ -218,6 +229,11 @@ export async function validateReleaseTooling(cwd, readFileFn = readFileDefault)
218229
"steps.alpha.outputs.create_tag == 'true'",
219230
"ci.yml must create the Go tag only when the alpha train needs it",
220231
);
232+
assertContains(
233+
ciWorkflow,
234+
"steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true'",
235+
"ci.yml must check out an existing Go tag before recovering GoReleaser",
236+
);
221237
assertContains(
222238
ciWorkflow,
223239
"steps.alpha.outputs.run_goreleaser == 'true'",

scripts/release-alpha-train.mjs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ export async function prepareAlphaReleaseTrain(options = {}) {
5353
if (state.skipReason) {
5454
throw new Error(`alpha release train is not ready to complete: ${state.skipReason}`);
5555
}
56-
if (state.tagExists && !state.tagMatchesHead) {
57-
throw new Error(
58-
`release tag ${state.tagName} already exists at ${state.tagCommit}, not ${state.headCommit}`,
59-
);
60-
}
6156
if (state.releaseExists && !state.imageExists) {
6257
throw new Error(
6358
`GitHub Release ${state.tagName} exists but ${state.image} is missing; recover that partial release manually before rerunning the alpha train`,
@@ -144,32 +139,6 @@ export async function inspectAlphaReleaseTrain(options = {}) {
144139
};
145140
}
146141

147-
if (!published && tagExists && !tagMatchesHead) {
148-
return {
149-
version,
150-
tagName,
151-
title,
152-
image,
153-
packages,
154-
activeChangesets,
155-
headCommit,
156-
tagCommit,
157-
tagExists,
158-
tagMatchesHead,
159-
releaseExists: false,
160-
imageExists: false,
161-
shouldComplete: false,
162-
shouldCreateTag: false,
163-
shouldRunGoreleaser: false,
164-
shouldUpdateRelease: false,
165-
skipReason: `version ${version} was already released from ${tagCommit}`,
166-
};
167-
}
168-
169-
if (tagExists && !tagMatchesHead) {
170-
throw new Error(`release tag ${tagName} already exists at ${tagCommit}, not ${headCommit}`);
171-
}
172-
173142
const releaseExists = remote ? await githubReleaseExists(tagName, execFileFn, cwd) : false;
174143
const imageExists = remote ? await containerImageExists(image, execFileFn, cwd) : false;
175144
if (releaseExists && !imageExists) {

0 commit comments

Comments
 (0)