diff --git a/.changeset/calm-files-warn.md b/.changeset/calm-files-warn.md new file mode 100644 index 00000000..bd3bf895 --- /dev/null +++ b/.changeset/calm-files-warn.md @@ -0,0 +1,5 @@ +--- +"@changesets/action": patch +--- + +Allow custom publish scripts to complete without a Changesets output file, warning that GitHub releases and git tags cannot be created when that file is missing. diff --git a/src/run.test.ts b/src/run.test.ts index 24051d23..180df1e4 100644 --- a/src/run.test.ts +++ b/src/run.test.ts @@ -1,10 +1,11 @@ import path from "node:path"; +import * as core from "@actions/core"; import type { Changeset } from "@changesets/types"; import { writeChangeset } from "@changesets/write"; import { createFixture } from "fs-fixture"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { GitHub } from "./github.ts"; -import { runVersion } from "./run.ts"; +import { runPublish, runVersion } from "./run.ts"; vi.mock("@actions/github", () => ({ context: { @@ -20,6 +21,10 @@ vi.mock("@actions/github", () => ({ graphql: mockedGraphql, }), })); +vi.mock("@actions/core", async (importOriginal) => ({ + ...(await importOriginal()), + warning: vi.fn(), +})); vi.mock("@changesets/ghcommit"); let mockedGithubMethods = { @@ -102,6 +107,59 @@ beforeEach(() => { vi.clearAllMocks(); }); +afterEach(() => { + vi.unstubAllEnvs(); +}); + +describe("publish", () => { + it("warns when a custom publish script does not create the output file", async () => { + await using fixture = await createSimpleProjectFixture(); + const cwd = fixture.path; + vi.stubEnv("RUNNER_TEMP", cwd); + + const result = await runPublish({ + script: 'node -e "void 0"', + github: createGithub(cwd), + createGithubReleases: true, + pushGitTags: true, + cwd, + }); + + expect(result).toEqual({ published: false, exitCode: 0 }); + expect(core.warning).toHaveBeenCalledWith( + expect.stringContaining( + "GitHub releases and git tags cannot be created without this output", + ), + ); + }); + + it("throws when the built-in publish command does not create the output file", async () => { + await using fixture = await createFixture({ + "node_modules/@changesets/cli/package.json": JSON.stringify({ + name: "@changesets/cli", + type: "module", + }), + "node_modules/@changesets/cli/bin.js": "", + "package.json": JSON.stringify({ + name: "simple-project", + version: "1.0.0", + }), + "package-lock.json": "", + }); + const cwd = fixture.path; + vi.stubEnv("RUNNER_TEMP", cwd); + + await expect( + runPublish({ + github: createGithub(cwd), + createGithubReleases: true, + pushGitTags: true, + cwd, + }), + ).rejects.toThrow("Failed to read changesets output at"); + }); +}); + describe("version", () => { it("creates simple PR", async () => { await using fixture = await createSimpleProjectFixture(); diff --git a/src/run.ts b/src/run.ts index 6f34e6b7..67783083 100644 --- a/src/run.ts +++ b/src/run.ts @@ -79,6 +79,8 @@ type ChangesetsOutputEvent = { packageName: string; }; +class ChangesetsOutputReadError extends Error {} + type PublishResult = | { published: true; @@ -113,9 +115,10 @@ async function readChangesetsOutput(outputPath: string) { try { rawOutput = await fs.readFile(outputPath, "utf8"); } catch (err) { - throw new Error(`Failed to read changesets output at ${outputPath}`, { - cause: err, - }); + throw new ChangesetsOutputReadError( + `Failed to read changesets output at ${outputPath}`, + { cause: err }, + ); } const events: ChangesetsOutputEvent[] = []; @@ -195,7 +198,18 @@ export async function runPublish({ let { packages, tool } = await getPackages(cwd); let packagesByName = new Map(packages.map((x) => [x.packageJson.name, x])); - let output = await readChangesetsOutput(outputFile); + let output: ChangesetsOutputEvent[]; + try { + output = await readChangesetsOutput(outputFile); + } catch (err) { + if (!script || !(err instanceof ChangesetsOutputReadError)) { + throw err; + } + core.warning( + `${err.message}. GitHub releases and git tags cannot be created without this output. Ensure the custom publish script passes CHANGESETS_OUTPUT to the Changesets CLI.`, + ); + output = []; + } let releases = output.map((event) => { let pkg = packagesByName.get(event.packageName); if (pkg === undefined) {