From a47cf15a60e6b38818e85ebc4a77981ed24ad03a Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sat, 5 Sep 2026 09:17:33 -0400 Subject: [PATCH] Guard against unresolved catalog:/workspace: protocols in published manifests Fixes #1052. @solid-primitives/event-listener@3.0.0-next.4 and @solid-primitives/pagination@1.0.0-next.7 were published to npm with a literal, unresolved "catalog:peer" string left in peerDependencies instead of a resolved semver range, breaking plain npm/yarn installs with EUNSUPPORTEDPROTOCOL. Root cause in that specific CI run isn't conclusively provable after the fact (local repro with matching pnpm 11.9.0 resolves correctly today, and the OIDC npm-upgrade step some would suspect postdates the broken commit), so the fix is root-cause agnostic: verify the actual packed manifest before publishing, not the source files. - scripts/verify-published-manifests.ts: packs the whole workspace via `pnpm pack -r --json`, reads each packed package/package.json, and fails loudly if any dependency field still contains an unresolved catalog:/workspace: protocol string. - Wire `pnpm run verify:manifests` into both release/release-tagged scripts, gating changeset publish. - Add the same check to tests.yml for earlier feedback on every PR. - Changeset republishes event-listener and pagination with correctly resolved peerDependencies once this guard is in place. --- .changeset/fix-catalog-protocol-republish.md | 6 ++ .github/workflows/tests.yml | 3 + package.json | 5 +- scripts/verify-published-manifests.ts | 72 ++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-catalog-protocol-republish.md create mode 100644 scripts/verify-published-manifests.ts diff --git a/.changeset/fix-catalog-protocol-republish.md b/.changeset/fix-catalog-protocol-republish.md new file mode 100644 index 000000000..10756de2f --- /dev/null +++ b/.changeset/fix-catalog-protocol-republish.md @@ -0,0 +1,6 @@ +--- +"@solid-primitives/event-listener": patch +"@solid-primitives/pagination": patch +--- + +Republish only, no functional changes. `event-listener@3.0.0-next.4` and `pagination@1.0.0-next.7` were accidentally published to npm with an unresolved pnpm workspace-catalog protocol string (`"catalog:peer"`) left in `peerDependencies`, instead of a resolved semver range. npm and yarn have no concept of the `catalog:` protocol, so installing either of those exact versions fails with `EUNSUPPORTEDPROTOCOL`. This release republishes both packages with `peerDependencies` correctly resolved (e.g. `"solid-js": "^2.0.0-rc.0"`). If you're on `event-listener@3.0.0-next.4` or `pagination@1.0.0-next.7`, upgrade to this version or later. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 82330fe00..54275fc11 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,6 +29,9 @@ jobs: - name: Build all packages run: pnpm build + - name: Verify no unresolved workspace protocols in packed manifests + run: pnpm run verify:manifests + - name: Lint # Will run the step even if build step failed if: success() || failure() diff --git a/package.json b/package.json index deb1f5f99..5d8e73cf5 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,10 @@ "jsr:sync-versions:check": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ./scripts/sync-jsr-versions.ts --check", "update-readme": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ./scripts/update-readme.ts", "measure": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ./scripts/measure.ts", + "verify:manifests": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ./scripts/verify-published-manifests.ts", "version": "changeset version && pnpm jsr:sync-versions && pnpm i --no-frozen-lockfile && git add .", - "release": "pnpm build && changeset publish", - "release-tagged": "pnpm build && if [ -f .changeset/pre.json ]; then changeset publish; else changeset publish --tag \"$BRANCH_NAME\"; fi" + "release": "pnpm build && pnpm run verify:manifests && changeset publish", + "release-tagged": "pnpm build && pnpm run verify:manifests && if [ -f .changeset/pre.json ]; then changeset publish; else changeset publish --tag \"$BRANCH_NAME\"; fi" }, "devDependencies": { "@babel/core": "catalog:", diff --git a/scripts/verify-published-manifests.ts b/scripts/verify-published-manifests.ts new file mode 100644 index 000000000..cb6a29251 --- /dev/null +++ b/scripts/verify-published-manifests.ts @@ -0,0 +1,72 @@ +// Guards against unresolved pnpm workspace-protocol strings (`catalog:`, +// `catalog:`, `workspace:`) making it into a package's PUBLISHED +// manifest. Source package.json files are expected to contain these +// protocols — pnpm resolves them at pack/publish time. This script actually +// packs every package (via `pnpm pack`, the same manifest-resolution path +// `pnpm publish` uses) and inspects the packed package/package.json, not the +// source file, so it only fails on a genuine resolution failure. +// +// Background: @solid-primitives/event-listener@3.0.0-next.4 and +// @solid-primitives/pagination@1.0.0-next.7 were published to npm with +// literal "catalog:peer" strings left in peerDependencies, which breaks +// plain npm/yarn installs (EUNSUPPORTEDPROTOCOL). See +// https://github.com/solidjs-community/solid-primitives/issues/1052. +// +// Usage: pnpm run verify:manifests +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { execFileSync } from "node:child_process"; + +const repoRoot = path.resolve(import.meta.dirname, ".."); +const DEP_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"] as const; +const BAD_PROTOCOLS = ["catalog:", "workspace:"]; + +type PackedPackage = { name: string; version: string; filename: string }; + +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "verify-manifests-")); + +let packed: PackedPackage[]; +try { + const output = execFileSync( + "pnpm", + ["pack", "--recursive", "--json", "--pack-destination", tmpDir], + { cwd: repoRoot, encoding: "utf8", maxBuffer: 1024 * 1024 * 64 }, + ); + packed = JSON.parse(output); +} catch (err) { + fs.rmSync(tmpDir, { recursive: true, force: true }); + throw err; +} + +const failures: string[] = []; + +for (const pkg of packed) { + const manifestText = execFileSync("tar", ["-xzO", "-f", pkg.filename, "package/package.json"], { + encoding: "utf8", + }); + const manifest = JSON.parse(manifestText); + + for (const field of DEP_FIELDS) { + const deps = manifest[field]; + if (!deps) continue; + for (const [dep, range] of Object.entries(deps)) { + if (typeof range === "string" && BAD_PROTOCOLS.some(p => range.startsWith(p))) { + failures.push(`${pkg.name}@${pkg.version} ${field}.${dep} = "${range}"`); + } + } + } +} + +fs.rmSync(tmpDir, { recursive: true, force: true }); + +if (failures.length > 0) { + console.error("Unresolved workspace-protocol strings found in packed manifests:\n"); + for (const f of failures) console.error(` - ${f}`); + console.error( + "\nThese packages would be published to npm broken (npm/yarn can't resolve `catalog:`/`workspace:`). Aborting release.", + ); + process.exit(1); +} + +console.log(`Verified ${packed.length} packed manifests: no unresolved workspace protocols.`);