chore(build): split tsconfig, typecheck tests, add node:test runner test/ was never type-checked and had no runner. Adds tsconfig.base.json shared by a src project and a test project, an npm test script scoped to non-manual suites, and a CI step. Ports the profile store suite as proof. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 00:13:06 +0530
9 files changed,
114 insertions(+),
94 deletions(-)
M
.github/workflows/ci.yml
→
.github/workflows/ci.yml
@@ -32,6 +32,9 @@
- name: Typecheck run: npm run typecheck + - name: Test + run: npm test + - name: Verify npm package run: npm pack --dry-run > /dev/null
M
package.json
→
package.json
@@ -57,7 +57,8 @@ "typebox": "*",
"typescript": "^5.0.0" }, "scripts": { - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json", + "test": "node --import tsx --test \"test/{profile,deep-research,domains,cdp,util}/**/*-test.ts\"", "prepublishOnly": "tsc --noEmit" }, "publishConfig": {
M
test/manual/bridge-test.ts
→
test/manual/bridge-test.ts
@@ -3,7 +3,7 @@ * Manual integration test for CdpBridge against real Chrome.
* Requires: Chrome with remote debugging enabled (chrome://inspect/#remote-debugging). * Run: npx tsx test/manual/bridge-test.ts */ -import { createCdpBridge, type SendToClient, type CdpBridge } from "../../src/daemon/bridge"; +import { createCdpBridge, type SendToClient } from "../../src/daemon/bridge"; import type { WireRequest, WireResponse, WireEvent } from "../../src/daemon/protocol"; let passed = 0;@@ -35,12 +35,12 @@ const responses: WireResponse[] = [];
const events: WireEvent[] = []; let eventCount = 0; - const send: SendToClient = (clientId, msg) => { + const send: SendToClient = (_clientId, msg) => { if (msg.type === "response") responses.push(msg); else events.push(msg); }; - bridge.onEvent((evt, _targets) => { eventCount++; }); + bridge.onEvent((_evt, _targets) => { eventCount++; }); const req1: WireRequest = { type: "request", id: 1, method: "Target.getTargets" }; bridge.handleRequest(req1, "pi-test", send);
M
test/manual/daemon-transport-test.ts
→
test/manual/daemon-transport-test.ts
@@ -7,7 +7,7 @@ * Run: npx tsx test/manual/daemon-transport-test.ts
*/ import { createIpcServer } from "../../src/daemon/server"; import { createDaemonTransport } from "../../src/cdp/daemon-transport"; -import { DAEMON_SOCKET_PATH, serialize } from "../../src/daemon/protocol"; +import { DAEMON_SOCKET_PATH } from "../../src/daemon/protocol"; import type { WireMessage } from "../../src/daemon/protocol"; import { unlinkSync } from "node:fs";
M
test/manual/ipc-server-test.ts
→
test/manual/ipc-server-test.ts
@@ -7,7 +7,7 @@ import { createConnection } from "node:net";
import { createInterface } from "node:readline"; import { createIpcServer } from "../../src/daemon/server"; import { DAEMON_SOCKET_PATH } from "../../src/daemon/protocol"; -import type { WireMessage } from "../../src/daemon/protocol"; +import type { WireEvent, WireMessage } from "../../src/daemon/protocol"; const TEST_CLIENT_ID = "pi-test-001";@@ -76,7 +76,11 @@ check(response != null, "Client received response from server");
// ── Test 4: Broadcast ───────────────────────────────────────────────── receivedByClient.length = 0; - const broadcastMsg = { type: "event", method: "Target.targetCreated", params: { targetInfo: { type: "page" } } }; + const broadcastMsg: WireEvent = { + type: "event", + method: "Target.targetCreated", + params: { targetInfo: { type: "page" } }, + }; server.broadcast(broadcastMsg); await sleep(200);@@ -93,7 +97,6 @@ check(server.clientCount() === 0, "Client count is 0 after disconnect");
// ── Test 6: Re-registration (same clientId after disconnect) ────────── const socket2 = createConnection(DAEMON_SOCKET_PATH); - const rl2 = createInterface({ input: socket2, crlfDelay: Infinity }); await new Promise<void>((resolve) => socket2.on("connect", resolve)); socket2.write(JSON.stringify({ type: "control", action: "register", clientId: TEST_CLIENT_ID }) + "\n");
M
test/profile/store-test.ts
→
test/profile/store-test.ts
@@ -4,24 +4,14 @@ *
* Redirects pi's agent dir via $PI_CODING_AGENT_DIR so the real * ~/.pi/agent/browser-harness.json is never touched. * - * Run: npx tsx test/profile/store-test.ts + * Run: npm test */ +import { test, before, after, describe } from "node:test"; +import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -let passed = 0; -let failed = 0; -const check = (cond: boolean, label: string): void => { - if (cond) { - passed++; - console.log(` ✓ ${label}`); - } else { - failed++; - console.error(` ✗ ${label}`); - } -}; - const pin = { userDataDir: "/home/u/.config/google-chrome", profileDir: "Profile 1",@@ -29,70 +19,87 @@ label: "Aman (aman@example.com)",
savedAt: new Date(0).toISOString(), }; -async function main(): Promise<void> { - const sandbox = mkdtempSync(join(tmpdir(), "pi-pin-test-")); - process.env["PI_CODING_AGENT_DIR"] = sandbox; +describe("profile pin store", () => { + let sandbox: string; + + before(() => { + sandbox = mkdtempSync(join(tmpdir(), "pi-pin-test-")); + process.env["PI_CODING_AGENT_DIR"] = sandbox; + }); - // Imported after the env override so paths.ts resolves into the sandbox. - const { pinFilePath } = await import("../../src/profile/paths"); - const { readPin, writePin, clearPin } = await import("../../src/profile/store"); + after(() => { + rmSync(sandbox, { recursive: true, force: true }); + }); - // P1: agent dir honours the env override - check(pinFilePath() === join(sandbox, "browser-harness.json"), "P1: pin path follows $PI_CODING_AGENT_DIR"); + test("P1: pin path follows $PI_CODING_AGENT_DIR", async () => { + const { pinFilePath } = await import("../../src/profile/paths"); + assert.equal(pinFilePath(), join(sandbox, "browser-harness.json")); + }); - // P2: nothing written yet - check((await readPin()) === null, "P2: absent file reads as no pin"); + test("P2: absent file reads as no pin", async () => { + const { readPin } = await import("../../src/profile/store"); + assert.equal(await readPin(), null); + }); - // P3: round trip - { + test("P3: write succeeds and round-trips", async () => { + const { readPin, writePin } = await import("../../src/profile/store"); const written = await writePin(pin); - check(written.success, "P3: write succeeds"); + assert.equal(written.success, true); const read = await readPin(); - check(read?.profileDir === "Profile 1", "P3: profileDir round-trips"); - check(read?.userDataDir === pin.userDataDir, "P3: userDataDir round-trips"); - check(read?.label === pin.label, "P3: label round-trips"); - } + assert.equal(read?.profileDir, "Profile 1"); + assert.equal(read?.userDataDir, pin.userDataDir); + assert.equal(read?.label, pin.label); + }); - // P4: no temp files survive an atomic write - check(readdirSync(sandbox).filter((f) => f.endsWith(".tmp")).length === 0, "P4: write leaves no .tmp files"); + test("P4: write leaves no .tmp files", () => { + assert.equal(readdirSync(sandbox).filter((f) => f.endsWith(".tmp")).length, 0); + }); - // P5: overwrite replaces rather than appends - { + test("P5: second write replaces the first", async () => { + const { readPin, writePin } = await import("../../src/profile/store"); await writePin({ ...pin, profileDir: "Default", label: "Work (work@example.com)" }); const read = await readPin(); - check(read?.profileDir === "Default", "P5: second write replaces the first"); - check(readdirSync(sandbox).filter((f) => f === "browser-harness.json").length === 1, "P5: one pin file on disk"); - } + assert.equal(read?.profileDir, "Default"); + assert.equal(readdirSync(sandbox).filter((f) => f === "browser-harness.json").length, 1); + }); - // P6: clear - { + test("P6: clear succeeds and cleared pin reads as none", async () => { + const { readPin, clearPin } = await import("../../src/profile/store"); const cleared = await clearPin(); - check(cleared.success, "P6: clear succeeds"); - check((await readPin()) === null, "P6: cleared pin reads as none"); - } + assert.equal(cleared.success, true); + assert.equal(await readPin(), null); + }); - // P7: corrupt file degrades to no pin - writeFileSync(pinFilePath(), "{ not json at all", "utf8"); - check((await readPin()) === null, "P7: malformed JSON reads as no pin"); + test("P7: malformed JSON reads as no pin", async () => { + const { readPin } = await import("../../src/profile/store"); + const { pinFilePath } = await import("../../src/profile/paths"); + writeFileSync(pinFilePath(), "{ not json at all", "utf8"); + assert.equal(await readPin(), null); + }); - // P8: unknown schema version is not guessed at - writeFileSync(pinFilePath(), JSON.stringify({ version: 99, profile: pin }), "utf8"); - check((await readPin()) === null, "P8: future version reads as no pin"); + test("P8: future version reads as no pin", async () => { + const { readPin } = await import("../../src/profile/store"); + const { pinFilePath } = await import("../../src/profile/paths"); + writeFileSync(pinFilePath(), JSON.stringify({ version: 99, profile: pin }), "utf8"); + assert.equal(await readPin(), null); + }); - // P9: structurally invalid pin is rejected - writeFileSync(pinFilePath(), JSON.stringify({ version: 1, profile: { profileDir: 42 } }), "utf8"); - check((await readPin()) === null, "P9: wrong field types read as no pin"); + test("P9: wrong field types read as no pin", async () => { + const { readPin } = await import("../../src/profile/store"); + const { pinFilePath } = await import("../../src/profile/paths"); + writeFileSync(pinFilePath(), JSON.stringify({ version: 1, profile: { profileDir: 42 } }), "utf8"); + assert.equal(await readPin(), null); + }); - // P10: a missing agent dir is created on write - { + test("P10: write recreates a missing agent dir", async () => { + const { readPin, writePin } = await import("../../src/profile/store"); rmSync(sandbox, { recursive: true, force: true }); const written = await writePin(pin); - check(written.success, "P10: write recreates a missing agent dir"); - check((await readPin())?.profileDir === "Profile 1", "P10: pin readable after dir recreation"); - } + assert.equal(written.success, true); + assert.equal((await readPin())?.profileDir, "Profile 1"); + }); - // P11: an unwritable location surfaces as an error result, not a throw - { + test("P11: write into an invalid dir returns an error result", async () => { const blocked = join(sandbox, "blocked"); mkdirSync(blocked, { recursive: true }); writeFileSync(join(blocked, "browser-harness.json"), "{}", "utf8");@@ -100,17 +107,7 @@ // Point the agent dir at a FILE — mkdir/write must fail cleanly.
process.env["PI_CODING_AGENT_DIR"] = join(blocked, "browser-harness.json"); const { writePin: writeAgain } = await import(`../../src/profile/store?nocache=${Date.now()}`); const written = await writeAgain(pin); - check(!written.success, "P11: write into an invalid dir returns an error result"); + assert.equal(written.success, false); process.env["PI_CODING_AGENT_DIR"] = sandbox; - } - - rmSync(sandbox, { recursive: true, force: true }); - - console.log(`\n${passed} passed, ${failed} failed`); - process.exit(failed === 0 ? 0 : 1); -} - -main().catch((e) => { - console.error("profile store test failed:", e); - process.exit(1); + }); });
A
tsconfig.base.json
@@ -0,0 +1,22 @@
+{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitOverride": true, + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true, + "esModuleInterop": true, + "skipLibCheck": true + } +}
M
tsconfig.json
→
tsconfig.json
@@ -1,21 +1,6 @@
{ + "extends": "./tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, - "noPropertyAccessFromIndexSignature": true, - "forceConsistentCasingInFileNames": true, - "esModuleInterop": true, - "skipLibCheck": true, "outDir": "./dist", "rootDir": "./src", "declaration": true,
A
tsconfig.test.json
@@ -0,0 +1,9 @@
+{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": ["node"] + }, + "include": ["src/**/*.ts", "test/**/*.ts"], + "exclude": ["node_modules", "dist"] +}