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
5 changes: 5 additions & 0 deletions .changeset/tidy-eyes-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/cli": patch
---

Fixed already-published version detection with npm 12, which always wraps successful `npm info --json` output in an array. The unwrapped output made `changeset publish` treat every package as unpublished and fail attempting to republish existing versions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,47 @@ describe("publishPackages", () => {
).toBe(false);
});

it("detects already-published versions when npm 12 wraps info output in an array", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
}),
});

mockSpawnImplementation((cmd, args) => {
if (cmd === "npm" && args?.[0] === "info") {
return spawnResult(
JSON.stringify([
{
name: "pkg-a",
version: "1.0.0",
versions: ["1.0.0"],
},
])
);
}
return spawnResult("", 1);
});

const result = await publishPackages({
packages: (await getPackages(cwd)).packages,
access: "public",
preState: undefined,
});

expect(result).toEqual([]);
expect(
mockSpawn.mock.calls.some(
([cmd, args]) => cmd === "npm" && args?.[0] === "publish"
)
).toBe(false);
});

it("publishes a new prerelease when exact version fallback also 404s", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
Expand Down
10 changes: 9 additions & 1 deletion packages/cli/src/commands/publish/npm-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ export async function getTokenIsRequired() {
// queries return empty → no versions list → only-pre detection is not
// possible. Such packages (e.g. GitHub Packages with no auto-latest) are
// published with preState.tag rather than "latest".

// npm 12 always wraps successful `npm info --json` output in an array.
// Unwrap it so downstream consumers keep seeing a single manifest.
function normalizeInfoJson(parsed: any) {
// given we query the implicit `latest` tag or an exact version, the resulting array should have at most one element
return Array.isArray(parsed) ? parsed[0] : parsed;
}

export function getPackageInfo(packageJson: PackageJSON) {
return npmRequestQueue.add(async () => {
info(`npm info ${packageJson.name}`);
Expand Down Expand Up @@ -189,7 +197,7 @@ export function getPackageInfo(packageJson: PackageJSON) {
},
};
}
return jsonParse(result.stdout.toString());
return normalizeInfoJson(jsonParse(result.stdout.toString()));
});
}

Expand Down