From f988bb7ba1138cb261a7d474c7c20a1e18096b72 Mon Sep 17 00:00:00 2001 From: JounQin Date: Sat, 19 Sep 2026 13:05:25 +0800 Subject: [PATCH] Skip ignored and private packages in the add-changeset link The maintainer "add a changeset" link listed every changed workspace package, including ignored and private ones. Adding such a changeset then fails with "Mixed changesets that contain both ignored and not ignored packages are not allowed". Filter the changed packages with `@changesets/should-skip-package`, matching what `@changesets/cli add` does, so ignored packages, private packages (unless `privatePackages.version` is enabled) and packages without a `version` are no longer suggested. --- get-changed-packages.ts | 19 ++++-- package.json | 1 + pnpm-lock.yaml | 3 + test/index.test.ts | 148 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 6 deletions(-) diff --git a/get-changed-packages.ts b/get-changed-packages.ts index edee2ca..5480a0c 100644 --- a/get-changed-packages.ts +++ b/get-changed-packages.ts @@ -1,7 +1,9 @@ +// oxlint-disable import/max-dependencies -- This module deliberately coordinates several Changesets packages. import nodePath from "path"; import { assembleReleasePlan } from "@changesets/assemble-release-plan"; import { validateConfig } from "@changesets/config"; import { parseChangesetFile } from "@changesets/parse"; +import { shouldSkipPackage } from "@changesets/should-skip-package"; import type { NewChangeset, Package, @@ -306,12 +308,17 @@ export const getChangedPackages = async ({ // A root-only project has a single package covering the whole repository, // so there is no directory to narrow the changed files down to. - const changedPackages = - packages.tool.type === "root" - ? packages.packages - : packages.packages.filter((pkg) => - changedFiles.some((changedFile) => changedFile.startsWith(`${pkg.dir}/`)), - ); + const changedPackages = packages.packages.filter( + (pkg) => + (packages.tool.type === "root" || + changedFiles.some((changedFile) => changedFile.startsWith(`${pkg.dir}/`))) && + // Ignored and private (unless opted in) packages cannot be versioned, so + // they must not be suggested in the "add a changeset" link. + !shouldSkipPackage(pkg, { + ignore: configResult.config.ignore, + allowPrivatePackages: configResult.config.privatePackages.version, + }), + ); return { changedPackages: changedPackages.map((pkg) => pkg.packageJson.name), diff --git a/package.json b/package.json index b25a58f..4a473d0 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@changesets/assemble-release-plan": "^7.0.0", "@changesets/config": "^4.0.0", "@changesets/parse": "^1.0.0", + "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "@octokit/webhooks": "^9.8.4", "@sentry/node": "^6.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20a2f3e..51df586 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@changesets/parse': specifier: ^1.0.0 version: 1.0.0 + '@changesets/should-skip-package': + specifier: ^1.0.0 + version: 1.0.0 '@changesets/types': specifier: ^7.0.0 version: 7.0.0 diff --git a/test/index.test.ts b/test/index.test.ts index c3f8895..c01815f 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -59,6 +59,14 @@ const normalizeCommentBody = (body: string) => "filename=.changeset/.md", ); +function getCommentBody(requests: Array) { + const commentRequests = requests.filter((request) => request.path.includes("/comments")); + assert.equal(commentRequests.length, 1); + const body = commentRequests[0].body; + assert.ok(body && typeof body === "object" && "body" in body && typeof body.body === "string"); + return body.body; +} + type ChangedFile = [ { status: "added"; @@ -412,6 +420,7 @@ describe.concurrent("changeset-bot", () => { ".changeset/config.json": JSON.stringify({}), "package.json": JSON.stringify({ name: "root-package", + version: "1.0.0", }), "src/index.ts": [{ status: "added" }, "export {};"], }, @@ -537,6 +546,7 @@ thing }), "packages/a/package.json": JSON.stringify({ name: "pkg-a", + version: "1.0.0", }), "packages/a/index.ts": [{ status: "added" }, "export const a = true;"], "packages/b/package.json": JSON.stringify({ @@ -599,6 +609,7 @@ thing }), "packages/ab/package.json": JSON.stringify({ name: "pkg-ab", + version: "1.0.0", }), "packages/ab/index.ts": [{ status: "added" }, "export const ab = true;"], }, @@ -641,6 +652,141 @@ thing `); }); + it("does not include private packages in the add-changeset link", async ({ expect, task }) => { + const probot = setupProbot(task.id); + const { requests } = usePrState(server, { + files: { + ".changeset/config.json": JSON.stringify({}), + "package.json": JSON.stringify({ + name: "test", + workspaces: ["packages/*"], + }), + "packages/a/package.json": JSON.stringify({ + name: "pkg-a", + version: "1.0.0", + }), + "packages/a/index.ts": [{ status: "added" }, "export const a = true;"], + "packages/private/package.json": JSON.stringify({ + name: "pkg-private", + version: "1.0.0", + private: true, + }), + "packages/private/index.ts": [{ status: "added" }, "export const p = true;"], + }, + comments: [], + }); + + await probot.receive({ + name: "pull_request", + payload: pullRequestOpen, + } as never); + + const commentBody = getCommentBody(requests); + expect(commentBody).toContain("%22pkg-a%22%3A%20patch"); + expect(commentBody).not.toContain("pkg-private"); + }); + + it("includes private packages in the add-changeset link when opted in", async ({ + expect, + task, + }) => { + const probot = setupProbot(task.id); + const { requests } = usePrState(server, { + files: { + ".changeset/config.json": JSON.stringify({ + privatePackages: { version: true }, + }), + "package.json": JSON.stringify({ + name: "test", + workspaces: ["packages/*"], + }), + "packages/private/package.json": JSON.stringify({ + name: "pkg-private", + version: "1.0.0", + private: true, + }), + "packages/private/index.ts": [{ status: "added" }, "export const p = true;"], + }, + comments: [], + }); + + await probot.receive({ + name: "pull_request", + payload: pullRequestOpen, + } as never); + + const commentBody = getCommentBody(requests); + expect(commentBody).toContain("%22pkg-private%22%3A%20patch"); + }); + + it("does not include ignored packages in the add-changeset link", async ({ expect, task }) => { + const probot = setupProbot(task.id); + const { requests } = usePrState(server, { + files: { + ".changeset/config.json": JSON.stringify({ ignore: ["pkg-ignored"] }), + "package.json": JSON.stringify({ + name: "test", + workspaces: ["packages/*"], + }), + "packages/a/package.json": JSON.stringify({ + name: "pkg-a", + version: "1.0.0", + }), + "packages/a/index.ts": [{ status: "added" }, "export const a = true;"], + "packages/ignored/package.json": JSON.stringify({ + name: "pkg-ignored", + version: "1.0.0", + }), + "packages/ignored/index.ts": [{ status: "added" }, "export const i = true;"], + }, + comments: [], + }); + + await probot.receive({ + name: "pull_request", + payload: pullRequestOpen, + } as never); + + const commentBody = getCommentBody(requests); + expect(commentBody).toContain("%22pkg-a%22%3A%20patch"); + expect(commentBody).not.toContain("pkg-ignored"); + }); + + it("does not include packages without a version in the add-changeset link", async ({ + expect, + task, + }) => { + const probot = setupProbot(task.id); + const { requests } = usePrState(server, { + files: { + ".changeset/config.json": JSON.stringify({}), + "package.json": JSON.stringify({ + name: "test", + workspaces: ["packages/*"], + }), + "packages/a/package.json": JSON.stringify({ + name: "pkg-a", + version: "1.0.0", + }), + "packages/a/index.ts": [{ status: "added" }, "export const a = true;"], + "packages/unversioned/package.json": JSON.stringify({ + name: "pkg-unversioned", + }), + "packages/unversioned/index.ts": [{ status: "added" }, "export const u = true;"], + }, + comments: [], + }); + + await probot.receive({ + name: "pull_request", + payload: pullRequestOpen, + } as never); + + const commentBody = getCommentBody(requests); + expect(commentBody).toContain("%22pkg-a%22%3A%20patch"); + expect(commentBody).not.toContain("pkg-unversioned"); + }); + it("detects pnpm workspaces when building the add-changeset link", async ({ expect, task }) => { const probot = setupProbot(task.id); const { requests } = usePrState(server, { @@ -652,6 +798,7 @@ thing "pnpm-workspace.yaml": "packages:\n - packages/*\n", "packages/a/package.json": JSON.stringify({ name: "pkg-a", + version: "1.0.0", }), "packages/a/file.ts": [{ status: "added" }, "export const a = true;"], }, @@ -704,6 +851,7 @@ thing ".changeset/config.json": JSON.stringify({}), "package.json": JSON.stringify({ name: "root-package", + version: "1.0.0", }), "pnpm-workspace.yaml": "onlyBuiltDependencies:\n - esbuild\n", "src/index.ts": [{ status: "added" }, "export {};"],