@danypops/pi-packed 0.21.11 → 0.21.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { AuthenticatedRpcClient } from "@danypops/vehicle-client/rpc-client";
@@ -17,8 +17,18 @@ import {
17
17
  } from "../src/packages/installed.ts";
18
18
  import type { Installer, PkgInfo, Registry, SearchPage, UpdateOutcome, UpdatesSnapshot } from "../src/packages/package.ts";
19
19
 
20
+ const roots: string[] = [];
21
+ afterEach(() => {
22
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
23
+ });
24
+
25
+ function track(dir: string): string {
26
+ roots.push(dir);
27
+ return dir;
28
+ }
29
+
20
30
  function writePiHome(settings: unknown, nodeModules: Record<string, string> = {}): string {
21
- const dir = mkdtempSync(join(tmpdir(), "packed-pihome-"));
31
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-pihome-")));
22
32
  writeFileSync(join(dir, "settings.json"), JSON.stringify(settings));
23
33
  for (const [name, version] of Object.entries(nodeModules)) {
24
34
  const pkgDir = join(dir, "npm", "node_modules", name);
@@ -99,7 +109,7 @@ describe("readInstalledPackages", () => {
99
109
  });
100
110
 
101
111
  it("missing settings → empty", () => {
102
- expect(readInstalledPackages(mkdtempSync(join(tmpdir(), "packed-")))).toEqual([]);
112
+ expect(readInstalledPackages(track(mkdtempSync(join(tmpdir(), "packed-"))))).toEqual([]);
103
113
  });
104
114
  });
105
115
 
@@ -113,7 +123,7 @@ describe("readInstalledPackagesAcrossScopes", () => {
113
123
 
114
124
  it("reproduces the real jittor gap: a stale project-scoped pin is invisible to a global-only read, visible once project scope is included", () => {
115
125
  const home = writePiHome({ packages: ["npm:pi-global@1.0.0"] });
116
- const projectRoot = mkdtempSync(join(tmpdir(), "packed-project-"));
126
+ const projectRoot = track(mkdtempSync(join(tmpdir(), "packed-project-")));
117
127
  const projectHome = join(projectRoot, ".pi");
118
128
  mkdirSync(projectHome, { recursive: true });
119
129
  writeFileSync(join(projectHome, "settings.json"), JSON.stringify({ packages: ["npm:papyrus@0.21.2"] }));
@@ -128,7 +138,7 @@ describe("readInstalledPackagesAcrossScopes", () => {
128
138
 
129
139
  it("is unaffected by a missing project settings file", () => {
130
140
  const home = writePiHome({ packages: ["npm:pi-global@1.0.0"] });
131
- const projectRoot = mkdtempSync(join(tmpdir(), "packed-project-"));
141
+ const projectRoot = track(mkdtempSync(join(tmpdir(), "packed-project-")));
132
142
  expect(readInstalledPackagesAcrossScopes(home, projectRoot)).toEqual([
133
143
  { name: "pi-global", pinned: "1.0.0", installed: undefined, scope: "global" },
134
144
  ]);
@@ -214,13 +224,13 @@ class NoopInstaller implements Installer {
214
224
 
215
225
  describe("package.updates.project (daemon RPC wiring)", () => {
216
226
  it("computes a live, cross-scope drift check on demand, distinct from package.updates' own persisted global-only snapshot", async () => {
217
- const home = mkdtempSync(join(tmpdir(), "packed-updates-project-"));
227
+ const home = track(mkdtempSync(join(tmpdir(), "packed-updates-project-")));
218
228
  writeFileSync(join(home, "settings.json"), JSON.stringify({ packages: ["npm:pi-global@1.0.0"] }));
219
- const projectRoot = mkdtempSync(join(tmpdir(), "packed-updates-project-root-"));
229
+ const projectRoot = track(mkdtempSync(join(tmpdir(), "packed-updates-project-root-")));
220
230
  const projectHome = join(projectRoot, ".pi");
221
231
  mkdirSync(projectHome, { recursive: true });
222
232
  writeFileSync(join(projectHome, "settings.json"), JSON.stringify({ packages: ["npm:papyrus@0.21.2"] }));
223
- const stateDir = mkdtempSync(join(tmpdir(), "packed-updates-project-state-"));
233
+ const stateDir = track(mkdtempSync(join(tmpdir(), "packed-updates-project-state-")));
224
234
  const db = openDb(dbPath(stateDir));
225
235
  replaceAll(
226
236
  db,
@@ -249,7 +259,7 @@ describe("package.updates.project (daemon RPC wiring)", () => {
249
259
 
250
260
  describe("updates store", () => {
251
261
  it("roundtrips", async () => {
252
- const dir = mkdtempSync(join(tmpdir(), "packed-"));
262
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-")));
253
263
  const snap = { checkedAt: new Date().toISOString(), updates: [{ name: "a", installed: "1", latest: "2", detectedAt: "" }] };
254
264
  await saveUpdates(dir, snap);
255
265
  expect(await loadUpdates(dir)).toEqual(snap);
@@ -259,7 +269,7 @@ describe("updates store", () => {
259
269
 
260
270
  describe("watcher producer", () => {
261
271
  it("writes a snapshot on tick", async () => {
262
- const dir = mkdtempSync(join(tmpdir(), "packed-"));
272
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-")));
263
273
  let signalTick: ((snapshot: UpdatesSnapshot) => void) | undefined;
264
274
  const tick = new Promise<UpdatesSnapshot>((resolve) => {
265
275
  signalTick = resolve;
@@ -282,7 +292,7 @@ describe("watcher producer", () => {
282
292
 
283
293
  describe("catalog status", () => {
284
294
  it("stale when unsynced, fresh after sync", () => {
285
- const dir = mkdtempSync(join(tmpdir(), "packed-"));
295
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-")));
286
296
  expect(catalogStatus(dir, 6 * 3_600_000).stale).toBe(true);
287
297
  const db = openDb(`${dir}/packed.db`);
288
298
  replaceAll(db, [{ name: "a", version: "1" }], "test");
@@ -1,5 +1,5 @@
1
- import { afterAll, beforeAll, describe, expect, it } from "bun:test";
2
- import { mkdtempSync, readFileSync } from "node:fs";
1
+ import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test";
2
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import type { Server } from "bun";
@@ -7,6 +7,16 @@ import { buildIndex, generateIndex, indexPath, indexStatus, readIndex, writeInde
7
7
  import { dbPath, openDb, replaceAll } from "../src/packages/db.ts";
8
8
  import { HttpRegistry } from "../src/registry/registry.ts";
9
9
 
10
+ const roots: string[] = [];
11
+ afterEach(() => {
12
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
13
+ });
14
+
15
+ function track(dir: string): string {
16
+ roots.push(dir);
17
+ return dir;
18
+ }
19
+
10
20
  // Guards the "never bulk GitHub calls" constraint against regression: a
11
21
  // future edit could easily start threading a GitHub-commit fetcher through
12
22
  // buildIndex the same way scoreTarget does. A source-level check catches
@@ -89,7 +99,10 @@ describe("buildIndex", () => {
89
99
  registry = new HttpRegistry(`http://127.0.0.1:${server.port}`, 250, 0, 1, `http://127.0.0.1:${server.port}`);
90
100
  });
91
101
 
92
- afterAll(() => server.stop(true));
102
+ afterAll(() => {
103
+ server.stop(true);
104
+ rmSync(catalogDir, { recursive: true, force: true });
105
+ });
93
106
 
94
107
  it("builds one entry per cataloged package, skipping a lookup failure rather than failing the whole run", async () => {
95
108
  const index = await buildIndex(registry, catalogDir, { delayMs: 0, currentPiVersion: async () => "0.83.0" });
@@ -123,7 +136,7 @@ describe("buildIndex", () => {
123
136
 
124
137
  describe("buildIndex bounds", () => {
125
138
  it("never calls downloads() -- confirmed live to trigger npm's 429s at real catalog scale -- and truncates past maxPackages, marking the result", async () => {
126
- const dir = mkdtempSync(join(tmpdir(), "packed-index-bounds-"));
139
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-index-bounds-")));
127
140
  const db = openDb(dbPath(dir));
128
141
  replaceAll(
129
142
  db,
@@ -160,7 +173,7 @@ describe("buildIndex bounds", () => {
160
173
  });
161
174
 
162
175
  it("collapses two concurrent callers into one run -- confirmed live to matter: the daemon's own maintenance tick and an on-demand CLI build overlapping compounded into a shutdown the daemon's SIGTERM grace period couldn't outlast", async () => {
163
- const dir = mkdtempSync(join(tmpdir(), "packed-index-concurrent-"));
176
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-index-concurrent-")));
164
177
  const db = openDb(dbPath(dir));
165
178
  replaceAll(db, [{ name: "pi-one", version: "1.0.0" }], "test");
166
179
  db.close();
@@ -203,7 +216,7 @@ describe("buildIndex bounds", () => {
203
216
 
204
217
  describe("buildIndex incremental delta scanning", () => {
205
218
  it("partitions the catalog into New/Changed/Unchanged against the prior index -- Unchanged makes zero live registry calls, New and Changed do", async () => {
206
- const dir = mkdtempSync(join(tmpdir(), "packed-index-delta-"));
219
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-index-delta-")));
207
220
  const db = openDb(dbPath(dir));
208
221
  replaceAll(
209
222
  db,
@@ -275,7 +288,7 @@ describe("buildIndex incremental delta scanning", () => {
275
288
  });
276
289
 
277
290
  it("maxPackages bounds only the New+Changed live-call queue -- Unchanged entries beyond that count still complete", async () => {
278
- const dir = mkdtempSync(join(tmpdir(), "packed-index-delta-bound-"));
291
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-index-delta-bound-")));
279
292
  const db = openDb(dbPath(dir));
280
293
  // 5 unchanged entries alone already exceed maxPackages: 1 below.
281
294
  replaceAll(
@@ -323,7 +336,7 @@ describe("buildIndex incremental delta scanning", () => {
323
336
 
324
337
  describe("index persistence", () => {
325
338
  it("writes, reads, and reports staleness", async () => {
326
- const dir = mkdtempSync(join(tmpdir(), "packed-index-store-"));
339
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-index-store-")));
327
340
  const path = indexPath(dir);
328
341
  expect(readIndex(path)).toBeUndefined();
329
342
  expect(indexStatus(path, 1_000).stale).toBe(true);
@@ -343,7 +356,7 @@ describe("index persistence", () => {
343
356
  });
344
357
 
345
358
  it("generateIndex builds and writes in one call", async () => {
346
- const dir = mkdtempSync(join(tmpdir(), "packed-index-generate-"));
359
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-index-generate-")));
347
360
  const db = openDb(dbPath(dir));
348
361
  replaceAll(db, [{ name: "pi-alpha", version: "1.0.0" }], "npm:keywords:pi-package");
349
362
  db.close();
@@ -7,8 +7,8 @@
7
7
  * a broken fixture must genuinely be refused, a healthy one must genuinely
8
8
  * pass.
9
9
  */
10
- import { describe, expect, it } from "bun:test";
11
- import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
10
+ import { afterEach, describe, expect, it } from "bun:test";
11
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
12
12
  import { tmpdir } from "node:os";
13
13
  import { dirname, join } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
@@ -23,6 +23,16 @@ const BROKEN = join(FIXTURES, "broken-package");
23
23
  const NO_MANIFEST = join(FIXTURES, "no-manifest-package");
24
24
  const DEP_PACKAGE = join(FIXTURES, "dep-package");
25
25
 
26
+ const roots: string[] = [];
27
+ afterEach(() => {
28
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
29
+ });
30
+
31
+ function track(dir: string): string {
32
+ roots.push(dir);
33
+ return dir;
34
+ }
35
+
26
36
  /** Writes a fresh package into `dir` whose extension entry point has a real
27
37
  * runtime import (`packed-fixture-dep`, a `file:` dependency resolvable
28
38
  * fully offline) -- otherwise healthy, structurally identical to HEALTHY,
@@ -106,13 +116,46 @@ describe("HeadlessInstallValidator (real npm pack + tar extraction, no mocking)"
106
116
  const validator = new HeadlessInstallValidator();
107
117
  const result = await validator.validate(`npm:${HEALTHY}`);
108
118
  expect(result.ok).toBe(true);
109
- expect(result.extensions).toEqual([{ path: "extension/index.ts", ok: true }]);
119
+ expect(result.extensions).toHaveLength(1);
120
+ expect(result.extensions[0]).toMatchObject({ path: "extension/index.ts", ok: true });
121
+ }, 20_000);
122
+ });
123
+
124
+ describe("HeadlessInstallValidator (vehicle-client-pi pi-load-harness, non-gating diagnostic)", () => {
125
+ it("attaches all-three-path evidence for a real healthy package without changing its ok:true verdict", async () => {
126
+ const validator = new HeadlessInstallValidator();
127
+ const result = await validator.validate(`npm:${HEALTHY}`);
128
+ expect(result.ok).toBe(true);
129
+ expect(result.extensions).toHaveLength(1);
130
+ const paths = result.extensions[0]?.additionalLoadPaths;
131
+ expect(paths).toBeDefined();
132
+ expect(paths?.map((p) => p.path).sort()).toEqual(["jiti-try-native-false", "jiti-try-native-true", "native-esm"]);
133
+ expect(paths?.every((p) => p.ok)).toBe(true);
134
+ }, 20_000);
135
+
136
+ it("reports the broken fixture's additional paths as ok:true -- documents a real, deliberate difference in what's checked, not a bug", async () => {
137
+ // verifyLoadableUnderPi only checks that *importing* the module succeeds;
138
+ // it never calls the extension's exported factory the way mock-pi-cli's
139
+ // gating check does. BROKEN's factory only throws once invoked, so
140
+ // merely importing it succeeds on every path -- confirmed directly
141
+ // against the real child process, not assumed. The two checks answer
142
+ // genuinely different questions (import-time vs. factory-execution-time
143
+ // failure) and are complementary for exactly that reason; this test
144
+ // exists so a future change that makes them silently agree (e.g. an
145
+ // accidental factory invocation creeping into the load-path check)
146
+ // gets caught as a real behavior change.
147
+ const validator = new HeadlessInstallValidator();
148
+ const result = await validator.validate(`npm:${BROKEN}`);
149
+ expect(result.ok).toBe(false); // the gating check still refuses it
150
+ const paths = result.extensions[0]?.additionalLoadPaths;
151
+ expect(paths).toBeDefined();
152
+ expect(paths?.every((p) => p.ok === true)).toBe(true);
110
153
  }, 20_000);
111
154
  });
112
155
 
113
156
  describe("HeadlessInstallValidator (bug repro, packed-headlessinstallvalidator-never-installs-the): staged tarball's own declared dependencies are never installed before the load check", () => {
114
157
  it("approves an otherwise-healthy package whose entry point needs its one declared file: dependency", async () => {
115
- const dir = mkdtempSync(join(tmpdir(), "packed-install-validation-dep-fixture-"));
158
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-install-validation-dep-fixture-")));
116
159
  writePackageWithRealDependency(dir);
117
160
 
118
161
  // Ground truth this isn't a broken fixture: a plain `bun install` in an
@@ -131,7 +174,8 @@ describe("HeadlessInstallValidator (bug repro, packed-headlessinstallvalidator-n
131
174
  const validator = new HeadlessInstallValidator();
132
175
  const result = await validator.validate(`npm:${dir}`);
133
176
  expect(result.ok).toBe(true);
134
- expect(result.extensions).toEqual([{ path: "extension/index.ts", ok: true }]);
177
+ expect(result.extensions).toHaveLength(1);
178
+ expect(result.extensions[0]).toMatchObject({ path: "extension/index.ts", ok: true });
135
179
  }, 30_000);
136
180
  });
137
181
 
@@ -159,7 +203,7 @@ describe("ExecInstaller.install() -- refuses before ever spawning the real pi bi
159
203
  // step to cwd into -- /bin/true always exits 0 regardless, proving both
160
204
  // the real install spawn *and* the re-resolution spawn were reached (a
161
205
  // refused install never gets this far to find out).
162
- const piHome = mkdtempSync(join(tmpdir(), "packed-install-validation-pihome-"));
206
+ const piHome = track(mkdtempSync(join(tmpdir(), "packed-install-validation-pihome-")));
163
207
  mkdirSync(join(piHome, "npm"), { recursive: true });
164
208
  const installer = new ExecInstaller(
165
209
  "/bin/true",
@@ -10,8 +10,8 @@
10
10
  * always-"Updated"-regardless-of-outcome text, and assert the real on-disk
11
11
  * version diff is what actually drives reloadRequired/alreadyUpToDate.
12
12
  */
13
- import { describe, expect, it } from "bun:test";
14
- import { chmodSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { afterEach, describe, expect, it } from "bun:test";
14
+ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
15
15
  import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
17
17
  import { ExecInstaller } from "../src/packages/install.ts";
@@ -24,6 +24,16 @@ import { ExecInstaller } from "../src/packages/install.ts";
24
24
  * baked into the script file itself (not an env var) so it is immune to
25
25
  * Bun.spawn's default env snapshot not picking up late process.env writes.
26
26
  */
27
+ const roots: string[] = [];
28
+ afterEach(() => {
29
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
30
+ });
31
+
32
+ function track(dir: string): string {
33
+ roots.push(dir);
34
+ return dir;
35
+ }
36
+
27
37
  function writeFakePi(dir: string, rewrite?: { piHome: string; name: string; newVersion: string }): string {
28
38
  const script = join(dir, "fake-pi");
29
39
  const rewriteLine = rewrite
@@ -39,7 +49,7 @@ function writeFakePi(dir: string, rewrite?: { piHome: string; name: string; newV
39
49
  }
40
50
 
41
51
  function writePiHome(nodeModules: Record<string, string> = {}): string {
42
- const dir = mkdtempSync(join(tmpdir(), "packed-exec-pihome-"));
52
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-exec-pihome-")));
43
53
  mkdirSync(join(dir, "npm"), { recursive: true });
44
54
  for (const [name, version] of Object.entries(nodeModules)) {
45
55
  const pkgDir = join(dir, "npm", "node_modules", name);
@@ -73,7 +83,7 @@ function writeFakeNpm(dir: string, logFile: string, rewrite?: { piHome: string;
73
83
 
74
84
  describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguous exit-0 text", () => {
75
85
  it("pinned source, version genuinely unchanged: alreadyUpToDate, reloadRequired false", async () => {
76
- const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
86
+ const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
77
87
  const bin = writeFakePi(scriptDir);
78
88
  const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
79
89
  const piHome = writePiHome({ "@scope/pkg": "1.2.3" });
@@ -90,7 +100,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
90
100
  });
91
101
 
92
102
  it('unpinned source, already latest (pi still exits 0 and says "Updated"): alreadyUpToDate', async () => {
93
- const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
103
+ const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
94
104
  const bin = writeFakePi(scriptDir);
95
105
  const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
96
106
  const piHome = writePiHome({ plain: "0.5.0" });
@@ -106,7 +116,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
106
116
  });
107
117
 
108
118
  it("unpinned source, a real version change happens: reloadRequired true", async () => {
109
- const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
119
+ const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
110
120
  const piHome = writePiHome({ plain: "0.5.0" });
111
121
  const bin = writeFakePi(scriptDir, { piHome, name: "plain", newVersion: "0.6.0" });
112
122
  const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
@@ -122,7 +132,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
122
132
  });
123
133
 
124
134
  it("git: source (no npm resolution possible either side): conservatively assumes it may have changed", async () => {
125
- const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
135
+ const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
126
136
  const bin = writeFakePi(scriptDir);
127
137
  const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
128
138
  const piHome = writePiHome();
@@ -141,7 +151,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
141
151
 
142
152
  describe("ExecInstaller — forces full dependency re-resolution, not just the target's own subtree", () => {
143
153
  it("update() fixes a stale root-level sibling that the targeted pi update never touched", async () => {
144
- const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
154
+ const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
145
155
  // Simulates the confirmed live defect: after `pi update npm:@scope/leaf`,
146
156
  // the leaf's own version bumps, but a root-level sibling
147
157
  // (@scope/shared) that the freshly-updated leaf now needs a newer
@@ -170,7 +180,7 @@ describe("ExecInstaller — forces full dependency re-resolution, not just the t
170
180
  });
171
181
 
172
182
  it("install() also forces a full re-resolution after a successful pi install", async () => {
173
- const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
183
+ const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
174
184
  const piHome = writePiHome({ "@scope/shared": "1.0.0" });
175
185
  const bin = writeFakePi(scriptDir);
176
186
  const npmLog = join(scriptDir, "npm.log");
@@ -185,7 +195,7 @@ describe("ExecInstaller — forces full dependency re-resolution, not just the t
185
195
  });
186
196
 
187
197
  it("surfaces a failed re-resolution instead of silently reporting success", async () => {
188
- const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
198
+ const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
189
199
  const piHome = writePiHome({ plain: "0.5.0" });
190
200
  const bin = writeFakePi(scriptDir, { piHome, name: "plain", newVersion: "0.6.0" });
191
201
  const failingNpm = join(scriptDir, "fake-npm-fail");
@@ -1,5 +1,5 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { createGithubLastCommitAt, type FetchGithubLastCommitAt } from "../src/adoption/commit-freshness.ts";
@@ -30,8 +30,14 @@ class FakeRegistry implements Registry {
30
30
  }
31
31
  }
32
32
 
33
+ const roots: string[] = [];
34
+ afterEach(() => {
35
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
36
+ });
37
+
33
38
  function fixture(manifest: Record<string, unknown>, readme = ""): string {
34
39
  const root = mkdtempSync(join(tmpdir(), "packed-pack-"));
40
+ roots.push(root);
35
41
  writeFileSync(join(root, "package.json"), JSON.stringify(manifest));
36
42
  if (readme) writeFileSync(join(root, "README.md"), readme);
37
43
  return root;
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, it } from "bun:test";
2
- import { mkdtempSync } from "node:fs";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { AuthenticatedRpcClient } from "@danypops/vehicle-client/rpc-client";
@@ -17,6 +17,16 @@ import {
17
17
  } from "../src/pi/pi-version.ts";
18
18
  import type { VersionCommand } from "../src/publish/publish.ts";
19
19
 
20
+ const roots: string[] = [];
21
+ afterEach(() => {
22
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
23
+ });
24
+
25
+ function track(dir: string): string {
26
+ roots.push(dir);
27
+ return dir;
28
+ }
29
+
20
30
  class NoopRegistry implements Registry {
21
31
  async search(): Promise<SearchPage> {
22
32
  return { results: [], total: 0 };
@@ -288,8 +298,8 @@ describe("pi.status operation", () => {
288
298
  reg: new NoopRegistry(),
289
299
  inst: new NoopInstaller(),
290
300
  token: "test-token",
291
- stateDir: mkdtempSync(join(tmpdir(), "packed-pi-version-state-")),
292
- dataDir: mkdtempSync(join(tmpdir(), "packed-pi-version-data-")),
301
+ stateDir: track(mkdtempSync(join(tmpdir(), "packed-pi-version-state-"))),
302
+ dataDir: track(mkdtempSync(join(tmpdir(), "packed-pi-version-data-"))),
293
303
  piVersion: { check: async () => ({ current: "0.82.1", latest: "0.83.0", upToDate: false }) },
294
304
  });
295
305
  const client = new AuthenticatedRpcClient<OperationName, OperationInputs, OperationOutputs>("http://packed.test", "test-token", {
@@ -305,8 +315,8 @@ describe("pi.status operation", () => {
305
315
  reg: new NoopRegistry(),
306
316
  inst: new NoopInstaller(),
307
317
  token: "test-token",
308
- stateDir: mkdtempSync(join(tmpdir(), "packed-pi-version-state-")),
309
- dataDir: mkdtempSync(join(tmpdir(), "packed-pi-version-data-")),
318
+ stateDir: track(mkdtempSync(join(tmpdir(), "packed-pi-version-state-"))),
319
+ dataDir: track(mkdtempSync(join(tmpdir(), "packed-pi-version-data-"))),
310
320
  piVersion: { check: async () => ({}) },
311
321
  });
312
322
  const client = new AuthenticatedRpcClient<OperationName, OperationInputs, OperationOutputs>("http://packed.test", "test-token", {
@@ -1,9 +1,19 @@
1
- import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
1
+ import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { describe, expect, it } from "bun:test";
4
+ import { afterEach, describe, expect, it } from "bun:test";
5
5
  import { ensureClient, resolvePiBinForSpawn } from "../src/public/client.ts";
6
6
 
7
+ const roots: string[] = [];
8
+ afterEach(() => {
9
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
10
+ });
11
+
12
+ function track(dir: string): string {
13
+ roots.push(dir);
14
+ return dir;
15
+ }
16
+
7
17
  describe("ensureClient (packed daemon auto-spawn-or-wait decision)", () => {
8
18
  it("connects immediately without ever checking for a service or spawning, when already reachable", async () => {
9
19
  let spawnCalls = 0;
@@ -150,12 +160,12 @@ describe("resolvePiBinForSpawn", () => {
150
160
  });
151
161
 
152
162
  it("returns undefined when no PATH directory has an executable `pi`", () => {
153
- const dir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-"));
163
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-")));
154
164
  expect(resolvePiBinForSpawn({ PATH: dir })).toBeUndefined();
155
165
  });
156
166
 
157
167
  it("resolves the absolute path to an executable `pi` on PATH", () => {
158
- const dir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-"));
168
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-")));
159
169
  const piPath = join(dir, "pi");
160
170
  writeFileSync(piPath, "#!/bin/sh\necho pi\n");
161
171
  chmodSync(piPath, 0o755);
@@ -163,8 +173,8 @@ describe("resolvePiBinForSpawn", () => {
163
173
  });
164
174
 
165
175
  it("skips a non-executable `pi` earlier on PATH and resolves a later one", () => {
166
- const deadDir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-dead-"));
167
- const liveDir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-live-"));
176
+ const deadDir = track(mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-dead-")));
177
+ const liveDir = track(mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-live-")));
168
178
  writeFileSync(join(deadDir, "pi"), "not executable");
169
179
  chmodSync(join(deadDir, "pi"), 0o644);
170
180
  const livePi = join(liveDir, "pi");
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "bun:test";
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
2
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
@@ -32,8 +32,18 @@ class RegistryFixture implements Registry {
32
32
  }
33
33
  }
34
34
 
35
+ const roots: string[] = [];
36
+ afterEach(() => {
37
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
38
+ });
39
+
40
+ function track(dir: string): string {
41
+ roots.push(dir);
42
+ return dir;
43
+ }
44
+
35
45
  function project(overrides: Record<string, unknown> = {}): string {
36
- const root = mkdtempSync(join(tmpdir(), "packed-publish-"));
46
+ const root = track(mkdtempSync(join(tmpdir(), "packed-publish-")));
37
47
  writeFileSync(
38
48
  join(root, "package.json"),
39
49
  JSON.stringify({
@@ -84,7 +94,7 @@ class MultiRegistryFixture implements Registry {
84
94
  /** A two-package Bun workspace: packages/core (published, depended on) and
85
95
  * packages/ext (the one under test, declaring a dependency on core). */
86
96
  function workspace(extDependencyRange = "^1.0.0"): { root: string; corePath: string; extPath: string } {
87
- const root = mkdtempSync(join(tmpdir(), "packed-workspace-"));
97
+ const root = track(mkdtempSync(join(tmpdir(), "packed-workspace-")));
88
98
  writeFileSync(join(root, "package.json"), JSON.stringify({ name: "demo-workspace", private: true, workspaces: ["packages/*"] }));
89
99
  writeFileSync(join(root, "bun.lock"), "{}");
90
100
  const corePath = join(root, "packages", "core");
@@ -1,5 +1,5 @@
1
1
  import { afterAll, beforeAll, describe, expect, it } from "bun:test";
2
- import { mkdtempSync } from "node:fs";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import type { Server } from "bun";
@@ -86,6 +86,14 @@ describe("HttpRegistry vs DaemonRegistry", () => {
86
86
  let httpServer: Server<undefined>;
87
87
  let daemonServer: Server<undefined>;
88
88
  const daemonToken = "c".repeat(64);
89
+ // createApp() is invoked fresh on every request below, so this can grow
90
+ // past one entry -- every one of them still needs cleanup.
91
+ const stateDirs: string[] = [];
92
+ function trackedStateDir(): string {
93
+ const dir = mkdtempSync(join(tmpdir(), "packed-registry-contract-"));
94
+ stateDirs.push(dir);
95
+ return dir;
96
+ }
89
97
 
90
98
  beforeAll(() => {
91
99
  httpServer = Bun.serve({
@@ -110,13 +118,14 @@ describe("HttpRegistry vs DaemonRegistry", () => {
110
118
  reg: new InMemoryRegistry(),
111
119
  inst: new NoopInstaller(),
112
120
  token: daemonToken,
113
- stateDir: mkdtempSync(join(tmpdir(), "packed-registry-contract-")),
121
+ stateDir: trackedStateDir(),
114
122
  }).fetch(req),
115
123
  });
116
124
  });
117
125
  afterAll(() => {
118
126
  httpServer.stop(true);
119
127
  daemonServer.stop(true);
128
+ for (const dir of stateDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
120
129
  });
121
130
 
122
131
  registryContract("HttpRegistry (real Bun.serve npm-mock)", () => new HttpRegistry(`http://127.0.0.1:${httpServer.port}`, 2, 0, 1_000));
@@ -12,6 +12,11 @@ afterEach(() => {
12
12
  for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
13
13
  });
14
14
 
15
+ function track(dir: string): string {
16
+ roots.push(dir);
17
+ return dir;
18
+ }
19
+
15
20
  function piHome(settingsPackages: unknown[]): string {
16
21
  const root = mkdtempSync(join(tmpdir(), "packed-resources-"));
17
22
  roots.push(root);
@@ -149,8 +154,8 @@ function rpcClient(piHome: string) {
149
154
  reg: new NoopRegistry(),
150
155
  inst: new NoopInstaller(),
151
156
  token: "test-token",
152
- stateDir: mkdtempSync(join(tmpdir(), "packed-resources-state-")),
153
- dataDir: mkdtempSync(join(tmpdir(), "packed-resources-data-")),
157
+ stateDir: track(mkdtempSync(join(tmpdir(), "packed-resources-state-"))),
158
+ dataDir: track(mkdtempSync(join(tmpdir(), "packed-resources-data-"))),
154
159
  piHome,
155
160
  });
156
161
  return new AuthenticatedRpcClient<OperationName, OperationInputs, OperationOutputs>("http://packed.test", "test-token", {
@@ -1,12 +1,22 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { mkdtempSync, writeFileSync } from "node:fs";
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { PACKAGE_OPERATIONS, packagePermissionDecision, readSecuritySettings, writeSecuritySettings } from "../src/security/security.ts";
6
6
 
7
+ const roots: string[] = [];
8
+ afterEach(() => {
9
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
10
+ });
11
+
12
+ function track(dir: string): string {
13
+ roots.push(dir);
14
+ return dir;
15
+ }
16
+
7
17
  describe("package permission policy", () => {
8
18
  it("defaults every arbitrary-code and settings/install-root mutation to approval", () => {
9
- const dir = mkdtempSync(join(tmpdir(), "packed-security-"));
19
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-security-")));
10
20
  const settings = readSecuritySettings(dir);
11
21
  expect(settings).toEqual({ mutationApproval: "always" });
12
22
  expect(PACKAGE_OPERATIONS).toEqual([
@@ -72,11 +82,11 @@ describe("package permission policy", () => {
72
82
  });
73
83
 
74
84
  it("persists an explicit unsafe opt-out and migrates the prior storage key", async () => {
75
- const dir = mkdtempSync(join(tmpdir(), "packed-security-"));
85
+ const dir = track(mkdtempSync(join(tmpdir(), "packed-security-")));
76
86
  expect(await writeSecuritySettings(dir, { mutationApproval: "never" })).toEqual({ mutationApproval: "never" });
77
87
  expect(readSecuritySettings(dir)).toEqual({ mutationApproval: "never" });
78
88
 
79
- const legacyDir = mkdtempSync(join(tmpdir(), "packed-security-legacy-"));
89
+ const legacyDir = track(mkdtempSync(join(tmpdir(), "packed-security-legacy-")));
80
90
  writeFileSync(join(legacyDir, "security.json"), '{"installApproval":"never"}\n');
81
91
  expect(readSecuritySettings(legacyDir)).toEqual({ mutationApproval: "never" });
82
92
  });