@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.
- package/extension/src/tui.ts +124 -33
- package/package.json +2 -2
- package/service/src/adoption/install-validation.ts +48 -2
- package/service/src/adoption/verify-load-paths-child.mjs +55 -0
- package/service/test/advisories.test.ts +17 -9
- package/service/test/cleanup.test.ts +7 -2
- package/service/test/cli.test.ts +29 -13
- package/service/test/db.test.ts +8 -2
- package/service/test/doctor.test.ts +7 -2
- package/service/test/domain.test.ts +22 -12
- package/service/test/index.test.ts +22 -9
- package/service/test/install-validation.test.ts +50 -6
- package/service/test/install.test.ts +20 -10
- package/service/test/pack-score.test.ts +8 -2
- package/service/test/pi-version.test.ts +15 -5
- package/service/test/public-client.test.ts +16 -6
- package/service/test/publish.test.ts +13 -3
- package/service/test/registry-contract.test.ts +11 -2
- package/service/test/resources.test.ts +7 -2
- package/service/test/security.test.ts +15 -5
- package/service/test/service.test.ts +19 -9
- package/service/test/setup.test.ts +14 -4
|
@@ -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(() =>
|
|
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).
|
|
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).
|
|
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:
|
|
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
|
});
|