diff --git a/.github/workflows/sync-skills.yml b/.github/workflows/sync-skills.yml index 1753721..ed2d71a 100644 --- a/.github/workflows/sync-skills.yml +++ b/.github/workflows/sync-skills.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: 22.19.0 + node-version-file: .nvmrc cache: npm - name: Install development dependencies diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 1b21505..fcde9b9 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version-file: .nvmrc cache: npm - run: npm ci - run: npm run verify diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..e222811 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.19.0 diff --git a/docs/SPEC.md b/docs/SPEC.md index b0333d9..1e24c81 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -102,7 +102,7 @@ package at runtime rather than at build time. | --- | --- | --- | | `dependencies` | `pi-mcp-adapter` at the release-tested exact version (`2.19.0`) | imported at runtime; must survive `--omit=dev`; exact pin prevents an untested adapter feature default from changing the tool surface | | `peerDependencies` (all `"*"`) | `@earendil-works/pi-coding-agent`, `-ai`, `-tui`, `typebox` | Pi provides these at runtime; pinning them risks a duplicate, mismatched copy | -| `devDependencies` | pi core at the release-tested pin (`0.83.0`), `vitest`, `vite-tsconfig-paths`, `@biomejs/biome`, `typescript`, `@types/node` | typecheck against current pi types; never shipped | +| `devDependencies` | pi core at the release-tested pin (`0.83.0`), `vitest`, `@biomejs/biome`, `typescript`, `@types/node` | typecheck against current pi types; never shipped | Pi core MUST NOT appear in `dependencies`. diff --git a/package-lock.json b/package-lock.json index 7c01f11..2b0368b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@types/node": "^24.0.0", "typebox": "^1.3.8", "typescript": "^7.0.2", - "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.1.10" }, "engines": { @@ -591,9 +590,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -611,9 +607,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -631,9 +624,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -651,9 +641,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -2972,9 +2959,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2991,9 +2975,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3010,9 +2991,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3029,9 +3007,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3048,9 +3023,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3331,9 +3303,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3351,9 +3320,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3371,9 +3337,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3391,9 +3354,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3411,9 +3371,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3431,9 +3388,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4990,13 +4944,6 @@ "node": ">= 0.4" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true, - "license": "MIT" - }, "node_modules/google-auth-library": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", @@ -5062,9 +5009,9 @@ } }, "node_modules/hono": { - "version": "4.12.32", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", - "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.0.tgz", + "integrity": "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -5429,9 +5376,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5453,9 +5397,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5477,9 +5418,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5501,9 +5439,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6707,43 +6642,6 @@ } } }, - "node_modules/vite-tsconfig-paths": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz", - "integrity": "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.3" - }, - "peerDependencies": { - "vite": "*" - } - }, - "node_modules/vite-tsconfig-paths/node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "deprecated": "unmaintained", - "dev": true, - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/vitest": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", diff --git a/package.json b/package.json index 050b5e0..9a83681 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "@types/node": "^24.0.0", "typebox": "^1.3.8", "typescript": "^7.0.2", - "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.1.10" } } diff --git a/tests/package/lockfile.test.ts b/tests/package/lockfile.test.ts new file mode 100644 index 0000000..09482b1 --- /dev/null +++ b/tests/package/lockfile.test.ts @@ -0,0 +1,50 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +/** + * v0.1.0 shipped a lockfile that `npm install` accepted but `npm ci` rejected, so every CI run + * failed while local development stayed green. `npm ci` is stricter than `npm install`: it refuses + * a lockfile that does not already satisfy package.json instead of resolving the difference. + * + * The npm major matters too. A lockfile written by npm 11 can be unusable by the npm 10 bundled + * with the `engines` floor, so `.nvmrc` pins the floor and both workflows read it — that is what + * makes CI exercise the oldest supported npm. The dry run below only covers whatever npm is + * running it, which is why the pin is asserted rather than assumed. + */ +const repoRoot = fileURLToPath(new URL("../..", import.meta.url)); +const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")) as { + engines?: { node?: string }; +}; +const nvmrc = readFileSync(join(repoRoot, ".nvmrc"), "utf8").trim(); +const workflows = ["verify.yml", "sync-skills.yml"].map((name) => ({ + name, + body: readFileSync(join(repoRoot, ".github/workflows", name), "utf8"), +})); + +describe("dependency lockfile", () => { + it("is installable by npm ci, not just npm install", () => { + expect(() => + execFileSync("npm", ["ci", "--dry-run"], { + cwd: repoRoot, + encoding: "utf8", + stdio: ["ignore", "ignore", "pipe"], + }), + ).not.toThrow(); + }, 120_000); + + it("pins the toolchain to the engines floor so CI installs on the oldest supported npm", () => { + expect(pkg.engines?.node).toBe(`>=${nvmrc}`); + }); + + it("makes every workflow resolve Node from the single .nvmrc pin", () => { + for (const { name, body } of workflows) { + expect(body, `${name} must read node-version-file`).toContain("node-version-file: .nvmrc"); + expect(body, `${name} must not pin node-version separately`).not.toMatch( + /node-version:\s*\d/, + ); + } + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index d0d28c2..7d587c0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,6 @@ -import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; export default defineConfig({ - plugins: [tsconfigPaths()], test: { globals: true, include: ["tests/**/*.test.ts"],