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
3 changes: 3 additions & 0 deletions .changeset/fix-alpha-goreleaser-dirty-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
---

9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,9 @@ jobs:
# repository is public.
NPM_CONFIG_PROVENANCE: "false"

- name: Restore changesets after publish decision
run: git restore -- .changeset

- name: Inspect alpha release train candidate
id: alpha-status
env:
Expand Down Expand Up @@ -914,6 +917,12 @@ jobs:
git tag -a "$TAG" -m "$TITLE"
git push origin "$TAG"

- name: Check out existing Go release tag
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true' }}
env:
TAG: ${{ steps.alpha.outputs.tag }}
run: git checkout --detach "$TAG"

- name: Prune npm package tags for GoReleaser
if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
run: |
Expand Down
41 changes: 40 additions & 1 deletion apps/cli/tests/unit/scripts/check-alpha-release-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,39 @@ describe("check-alpha-release-plan script", () => {
).rejects.toThrow("must prune empty changesets before Changesets decides whether to publish");
});

it("rejects a workflow that leaves pruned changesets dirty for GoReleaser", async () => {
const { cwd, statusPath } = await fixtureRepo({
ciWorkflow: validCiWorkflow().replace(
[
" - name: Restore changesets after publish decision",
" run: git restore -- .changeset",
"",
].join("\n"),
"",
),
});

await expect(
checkAlphaReleasePlanModule.checkAlphaReleasePlan({ cwd, statusPath }),
).rejects.toThrow("must restore pruned changesets before inspecting or running GoReleaser");
});

it("rejects a workflow that cannot recover an existing Go release tag", async () => {
const { cwd, statusPath } = await fixtureRepo({
ciWorkflow: validCiWorkflow().replace(
[
" - if: ${{ steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true' }}",
" run: git checkout --detach \"$TAG\"",
].join("\n"),
"",
),
});

await expect(
checkAlphaReleasePlanModule.checkAlphaReleasePlan({ cwd, statusPath }),
).rejects.toThrow("must check out an existing Go tag before recovering GoReleaser");
});

it("rejects alpha release notes generated inside the checkout", async () => {
const { cwd, statusPath } = await fixtureRepo({
ciWorkflow: validCiWorkflow().replace(
Expand Down Expand Up @@ -264,7 +297,11 @@ function validCiWorkflow(): string {
" uses: changesets/action@v1",
" with:",
" createGithubReleases: false",
" - id: alpha-status",
" - name: Restore changesets after publish decision",
" run: git restore -- .changeset",
"",
" - name: Inspect alpha release train candidate",
" id: alpha-status",
" run: |",
" node scripts/release-alpha-train.mjs status --published \"$PUBLISHED\" --remote false",
" - if: ${{ steps.alpha-status.outputs.should_complete == 'true' }}",
Expand All @@ -275,6 +312,8 @@ function validCiWorkflow(): string {
" cat \"$alpha_env\" >> \"$GITHUB_OUTPUT\"",
" - if: ${{ steps.alpha.outputs.create_tag == 'true' }}",
" run: git tag \"$TAG\"",
" - if: ${{ steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true' }}",
" run: git checkout --detach \"$TAG\"",
" - if: ${{ steps.alpha.outputs.run_goreleaser == 'true' }}",
" run: goreleaser release --clean",
" - if: ${{ steps.alpha.outputs.update_release == 'true' }}",
Expand Down
38 changes: 21 additions & 17 deletions apps/cli/tests/unit/scripts/release-alpha-train.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,31 +121,35 @@ describe("release-alpha-train script", () => {
expect(result.shouldRunGoreleaser).toBe(true);
});

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

await expect(
releaseAlphaTrain.prepareAlphaReleaseTrain({
cwd,
execFile: commandMock({ tagCommit: "other-commit" }),
published: true,
}),
).rejects.toThrow("release tag v0.1.0-alpha.5 already exists at other-commit");
const result = await releaseAlphaTrain.prepareAlphaReleaseTrain({
cwd,
execFile: commandMock({ tagCommit: "other-commit" }),
published: true,
});

expect(result.shouldCreateTag).toBe(false);
expect(result.shouldRunGoreleaser).toBe(true);
expect(result.shouldUpdateRelease).toBe(true);
});

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

await expect(
releaseAlphaTrain.inspectAlphaReleaseTrain({
cwd,
execFile: commandMock({ tagCommit: "other-commit" }),
remote: false,
const result = await releaseAlphaTrain.prepareAlphaReleaseTrain({
cwd,
execFile: commandMock({
tagCommit: "other-commit",
releaseExists: true,
imageExists: true,
}),
).resolves.toMatchObject({
shouldComplete: false,
skipReason: "version 0.1.0-alpha.5 was already released from other-commit",
});

expect(result.shouldCreateTag).toBe(false);
expect(result.shouldRunGoreleaser).toBe(false);
expect(result.shouldUpdateRelease).toBe(true);
});

it("ignores prerelease-tracked and empty changesets when recovering a versioned train", async () => {
Expand Down
16 changes: 16 additions & 0 deletions scripts/check-alpha-release-plan.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,17 @@ export async function validateReleaseTooling(cwd, readFileFn = readFileDefault)
].join("\n"),
"release-alpha-train must prune empty changesets before Changesets decides whether to publish",
);
assertJobContains(
ciWorkflow,
"release-alpha-train",
[
" - name: Restore changesets after publish decision",
" run: git restore -- .changeset",
"",
" - name: Inspect alpha release train candidate",
].join("\n"),
"release-alpha-train must restore pruned changesets before inspecting or running GoReleaser",
);
assertContains(
ciWorkflow,
"node scripts/release-alpha-train.mjs status --published \"$PUBLISHED\" --remote false",
Expand Down Expand Up @@ -218,6 +229,11 @@ export async function validateReleaseTooling(cwd, readFileFn = readFileDefault)
"steps.alpha.outputs.create_tag == 'true'",
"ci.yml must create the Go tag only when the alpha train needs it",
);
assertContains(
ciWorkflow,
"steps.alpha.outputs.run_goreleaser == 'true' && steps.alpha.outputs.tag_exists == 'true'",
"ci.yml must check out an existing Go tag before recovering GoReleaser",
);
assertContains(
ciWorkflow,
"steps.alpha.outputs.run_goreleaser == 'true'",
Expand Down
31 changes: 0 additions & 31 deletions scripts/release-alpha-train.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ export async function prepareAlphaReleaseTrain(options = {}) {
if (state.skipReason) {
throw new Error(`alpha release train is not ready to complete: ${state.skipReason}`);
}
if (state.tagExists && !state.tagMatchesHead) {
throw new Error(
`release tag ${state.tagName} already exists at ${state.tagCommit}, not ${state.headCommit}`,
);
}
if (state.releaseExists && !state.imageExists) {
throw new Error(
`GitHub Release ${state.tagName} exists but ${state.image} is missing; recover that partial release manually before rerunning the alpha train`,
Expand Down Expand Up @@ -144,32 +139,6 @@ export async function inspectAlphaReleaseTrain(options = {}) {
};
}

if (!published && tagExists && !tagMatchesHead) {
return {
version,
tagName,
title,
image,
packages,
activeChangesets,
headCommit,
tagCommit,
tagExists,
tagMatchesHead,
releaseExists: false,
imageExists: false,
shouldComplete: false,
shouldCreateTag: false,
shouldRunGoreleaser: false,
shouldUpdateRelease: false,
skipReason: `version ${version} was already released from ${tagCommit}`,
};
}

if (tagExists && !tagMatchesHead) {
throw new Error(`release tag ${tagName} already exists at ${tagCommit}, not ${headCommit}`);
}

const releaseExists = remote ? await githubReleaseExists(tagName, execFileFn, cwd) : false;
const imageExists = remote ? await containerImageExists(image, execFileFn, cwd) : false;
if (releaseExists && !imageExists) {
Expand Down
Loading