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
2 changes: 1 addition & 1 deletion .github/workflows/sync-skills.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.19.0
2 changes: 1 addition & 1 deletion docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
108 changes: 3 additions & 105 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
50 changes: 50 additions & 0 deletions tests/package/lockfile.test.ts
Original file line number Diff line number Diff line change
@@ -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/,
);
}
});
});
2 changes: 0 additions & 2 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -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"],
Expand Down
Loading