@danypops/pi-packed 0.21.12 → 0.21.14
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/dist/client.d.ts +16 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -1
- package/dist/protocol.d.ts +6 -0
- package/dist/protocol.d.ts.map +1 -1
- package/extension/src/index.ts +9 -1
- package/extension/src/model.ts +35 -0
- package/extension/src/package-inspector.ts +12 -0
- package/extension/src/tabs/discover.ts +14 -1
- package/extension/src/tools.ts +31 -20
- package/extension/src/tui.ts +124 -33
- package/extension/src/vehicle-target.ts +24 -0
- package/extension/src/vehicle-tools.ts +84 -0
- package/package.json +2 -2
- package/service/src/adoption/install-validation.ts +19 -1
- package/service/src/daemon/service.ts +17 -1
- package/service/src/packages/install.ts +94 -13
- package/service/src/packages/package.ts +35 -0
- package/service/src/public/client.ts +29 -0
- package/service/src/public/protocol.ts +6 -0
- package/service/src/registry/registry.ts +20 -1
- package/service/src/setup/setup.ts +50 -2
- 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 +14 -4
- package/service/test/install.test.ts +129 -12
- package/service/test/npm-metadata-e2e.test.ts +25 -3
- package/service/test/pack-score.test.ts +8 -2
- package/service/test/perf/multi-install.perf.test.ts +222 -0
- 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 +174 -4
package/service/test/cli.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
|
1
|
+
import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { writeDaemonHandle } from "@danypops/vehicle-server/paths";
|
|
@@ -125,6 +125,16 @@ class FakeDaemonServiceInstaller {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
const roots: string[] = [];
|
|
129
|
+
afterEach(() => {
|
|
130
|
+
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
function track(dir: string): string {
|
|
134
|
+
roots.push(dir);
|
|
135
|
+
return dir;
|
|
136
|
+
}
|
|
137
|
+
|
|
128
138
|
function deps(over: Partial<CliDeps> = {}): CliDeps {
|
|
129
139
|
return {
|
|
130
140
|
reg: new FakeRegistry(),
|
|
@@ -138,8 +148,8 @@ function deps(over: Partial<CliDeps> = {}): CliDeps {
|
|
|
138
148
|
return { mutationApproval };
|
|
139
149
|
},
|
|
140
150
|
},
|
|
141
|
-
stateDir: mkdtempSync(join(tmpdir(), "packed-")),
|
|
142
|
-
piHome: mkdtempSync(join(tmpdir(), "packed-pihome-")),
|
|
151
|
+
stateDir: track(mkdtempSync(join(tmpdir(), "packed-"))),
|
|
152
|
+
piHome: track(mkdtempSync(join(tmpdir(), "packed-pihome-"))),
|
|
143
153
|
...over,
|
|
144
154
|
};
|
|
145
155
|
}
|
|
@@ -389,7 +399,7 @@ describe("CLI", () => {
|
|
|
389
399
|
it("updates --project also checks a project's own .pi/settings.json pins -- global-only misses them entirely", async () => {
|
|
390
400
|
const d = deps();
|
|
391
401
|
writeFileSync(join(d.piHome, "settings.json"), JSON.stringify({ packages: ["npm:pi-global@1.0.0"] }));
|
|
392
|
-
const projectRoot = mkdtempSync(join(tmpdir(), "packed-project-"));
|
|
402
|
+
const projectRoot = track(mkdtempSync(join(tmpdir(), "packed-project-")));
|
|
393
403
|
const projectHome = join(projectRoot, ".pi");
|
|
394
404
|
mkdirSync(projectHome, { recursive: true });
|
|
395
405
|
writeFileSync(join(projectHome, "settings.json"), JSON.stringify({ packages: ["npm:papyrus@0.21.2"] }));
|
|
@@ -938,7 +948,7 @@ describe("CLI", () => {
|
|
|
938
948
|
});
|
|
939
949
|
|
|
940
950
|
it("advisories runs standalone without a daemon and degrades to zero findings, never a real network call, when nothing is installed", async () => {
|
|
941
|
-
const d = deps({ piHome: mkdtempSync(join(tmpdir(), "packed-advisories-cli-")) });
|
|
951
|
+
const d = deps({ piHome: track(mkdtempSync(join(tmpdir(), "packed-advisories-cli-"))) });
|
|
942
952
|
const result = await cliRun(["advisories", "--json"], d);
|
|
943
953
|
expect(result.code).toBe(0);
|
|
944
954
|
expect(JSON.parse(result.out)).toEqual({ scanned: 0, findings: [], diagnostics: [], truncated: false });
|
|
@@ -956,7 +966,7 @@ describe("CLI", () => {
|
|
|
956
966
|
});
|
|
957
967
|
|
|
958
968
|
it("resources list and toggle run standalone without a daemon (CLI parity for the daemon-only resources.list/toggle operations)", async () => {
|
|
959
|
-
const piHome = mkdtempSync(join(tmpdir(), "packed-resources-cli-"));
|
|
969
|
+
const piHome = track(mkdtempSync(join(tmpdir(), "packed-resources-cli-")));
|
|
960
970
|
writeFileSync(join(piHome, "settings.json"), JSON.stringify({ packages: ["npm:pi-demo"] }));
|
|
961
971
|
const pkgDir = join(piHome, "npm", "node_modules", "pi-demo");
|
|
962
972
|
mkdirSync(pkgDir, { recursive: true });
|
|
@@ -998,7 +1008,7 @@ describe("CLI", () => {
|
|
|
998
1008
|
(bwrapUsable ? it : it.skip)(
|
|
999
1009
|
"doctor runs standalone without a daemon and reproduces the jittor incident through the real CLI (CLI parity for the daemon-only doctor.run operation)",
|
|
1000
1010
|
async () => {
|
|
1001
|
-
const piHome = mkdtempSync(join(tmpdir(), "packed-doctor-cli-"));
|
|
1011
|
+
const piHome = track(mkdtempSync(join(tmpdir(), "packed-doctor-cli-")));
|
|
1002
1012
|
writeFileSync(join(piHome, "settings.json"), JSON.stringify({ packages: ["npm:pi-papyrus"] }));
|
|
1003
1013
|
const globalPkg = join(piHome, "npm", "node_modules", "pi-papyrus");
|
|
1004
1014
|
mkdirSync(join(globalPkg, "extension"), { recursive: true });
|
|
@@ -1007,7 +1017,7 @@ describe("CLI", () => {
|
|
|
1007
1017
|
JSON.stringify({ name: "pi-papyrus", version: "1.0.0", pi: { extensions: ["extension/index.ts"] } }),
|
|
1008
1018
|
);
|
|
1009
1019
|
writeFileSync(join(globalPkg, "extension", "index.ts"), 'export default function (pi: any) { pi.registerTool({ name: "tasks" }); }');
|
|
1010
|
-
const projectRoot = mkdtempSync(join(tmpdir(), "packed-doctor-cli-project-"));
|
|
1020
|
+
const projectRoot = track(mkdtempSync(join(tmpdir(), "packed-doctor-cli-project-")));
|
|
1011
1021
|
const projectHome = join(projectRoot, ".pi");
|
|
1012
1022
|
mkdirSync(projectHome, { recursive: true });
|
|
1013
1023
|
writeFileSync(join(projectHome, "settings.json"), JSON.stringify({ packages: ["npm:papyrus"] }));
|
|
@@ -1063,12 +1073,14 @@ describe("CLI", () => {
|
|
|
1063
1073
|
describe("daemon client", () => {
|
|
1064
1074
|
let server: Server<undefined>;
|
|
1065
1075
|
let daemonDir: string;
|
|
1076
|
+
let daemonPiHome: string;
|
|
1066
1077
|
let daemonPaths: PackedPaths;
|
|
1067
1078
|
let daemonInstaller: FakeInstaller;
|
|
1068
1079
|
const daemonToken = "d".repeat(64);
|
|
1069
1080
|
|
|
1070
1081
|
beforeAll(async () => {
|
|
1071
1082
|
daemonDir = mkdtempSync(join(tmpdir(), "packed-daemon-"));
|
|
1083
|
+
daemonPiHome = mkdtempSync(join(tmpdir(), "packed-daemon-pi-"));
|
|
1072
1084
|
daemonPaths = resolvePackedPaths({ env: { PI_PACKED_HOME: daemonDir } });
|
|
1073
1085
|
writeFileSync(daemonPaths.token, `${daemonToken}\n`);
|
|
1074
1086
|
daemonInstaller = new FakeInstaller();
|
|
@@ -1100,7 +1112,7 @@ describe("daemon client", () => {
|
|
|
1100
1112
|
},
|
|
1101
1113
|
token: daemonToken,
|
|
1102
1114
|
stateDir: daemonDir,
|
|
1103
|
-
piHome:
|
|
1115
|
+
piHome: daemonPiHome,
|
|
1104
1116
|
packer: {
|
|
1105
1117
|
async verify(path) {
|
|
1106
1118
|
return {
|
|
@@ -1169,7 +1181,11 @@ describe("daemon client", () => {
|
|
|
1169
1181
|
server = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: (req) => app.fetch(req) });
|
|
1170
1182
|
writeDaemonHandle(daemonPaths.handle, { host: "127.0.0.1", port: server.port!, pid: process.pid });
|
|
1171
1183
|
});
|
|
1172
|
-
afterAll(() =>
|
|
1184
|
+
afterAll(() => {
|
|
1185
|
+
server.stop(true);
|
|
1186
|
+
rmSync(daemonDir, { recursive: true, force: true });
|
|
1187
|
+
rmSync(daemonPiHome, { recursive: true, force: true });
|
|
1188
|
+
});
|
|
1173
1189
|
|
|
1174
1190
|
it("probe finds a live daemon", async () => {
|
|
1175
1191
|
const found = await probe(daemonPaths);
|
|
@@ -1188,7 +1204,7 @@ describe("daemon client", () => {
|
|
|
1188
1204
|
});
|
|
1189
1205
|
|
|
1190
1206
|
it("probe rejects dead state", async () => {
|
|
1191
|
-
const directory = mkdtempSync(join(tmpdir(), "packed-"));
|
|
1207
|
+
const directory = track(mkdtempSync(join(tmpdir(), "packed-")));
|
|
1192
1208
|
expect(await probe(resolvePackedPaths({ env: { PI_PACKED_HOME: directory } }))).toBeUndefined();
|
|
1193
1209
|
});
|
|
1194
1210
|
|
|
@@ -1260,7 +1276,7 @@ describe("daemon client", () => {
|
|
|
1260
1276
|
it("resolveRegistry prefers daemon, falls back direct", async () => {
|
|
1261
1277
|
const viaDaemon = await resolveRegistry(daemonPaths, "https://registry.npmjs.org");
|
|
1262
1278
|
expect(viaDaemon).toBeInstanceOf(DaemonRegistry);
|
|
1263
|
-
const directory = mkdtempSync(join(tmpdir(), "packed-"));
|
|
1279
|
+
const directory = track(mkdtempSync(join(tmpdir(), "packed-")));
|
|
1264
1280
|
const direct = await resolveRegistry(resolvePackedPaths({ env: { PI_PACKED_HOME: directory } }), "https://registry.npmjs.org");
|
|
1265
1281
|
expect(direct).toBeInstanceOf(HttpRegistry);
|
|
1266
1282
|
});
|
package/service/test/db.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { existsSync, mkdtempSync } from "node:fs";
|
|
1
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { syncCatalog } from "../src/packages/catalog.ts";
|
|
@@ -127,9 +127,15 @@ class PagedRegistry implements Registry {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
const roots: string[] = [];
|
|
131
|
+
afterEach(() => {
|
|
132
|
+
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
133
|
+
});
|
|
134
|
+
|
|
130
135
|
describe("syncCatalog → SQLite", () => {
|
|
131
136
|
it("accumulates pages into the DB and records sync meta", async () => {
|
|
132
137
|
const dir = mkdtempSync(join(tmpdir(), "packed-"));
|
|
138
|
+
roots.push(dir);
|
|
133
139
|
const reg = new PagedRegistry({ 0: PKGS.slice(0, 2), 2: PKGS.slice(2) }, 3);
|
|
134
140
|
expect(await syncCatalog(reg, dir)).toBe(3);
|
|
135
141
|
expect(existsSync(dbPath(dir))).toBe(true);
|
|
@@ -13,6 +13,11 @@ afterEach(() => {
|
|
|
13
13
|
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
function track(dir: string): string {
|
|
17
|
+
roots.push(dir);
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
// Same sandbox-availability probe as smoke.test.ts: binary presence alone
|
|
17
22
|
// doesn't prove bwrap actually works under this host's user namespaces.
|
|
18
23
|
function bwrapUsable(): boolean {
|
|
@@ -216,8 +221,8 @@ describeIfSandboxed("doctor.run (daemon RPC wiring)", () => {
|
|
|
216
221
|
reg: new NoopRegistry(),
|
|
217
222
|
inst: new NoopInstaller(),
|
|
218
223
|
token: "test-token",
|
|
219
|
-
stateDir: mkdtempSync(join(tmpdir(), "packed-doctor-state-")),
|
|
220
|
-
dataDir: mkdtempSync(join(tmpdir(), "packed-doctor-data-")),
|
|
224
|
+
stateDir: track(mkdtempSync(join(tmpdir(), "packed-doctor-state-"))),
|
|
225
|
+
dataDir: track(mkdtempSync(join(tmpdir(), "packed-doctor-data-"))),
|
|
221
226
|
piHome: home,
|
|
222
227
|
});
|
|
223
228
|
const client = new AuthenticatedRpcClient<OperationName, OperationInputs, OperationOutputs>("http://packed.test", "test-token", {
|
|
@@ -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,
|
|
@@ -145,7 +155,7 @@ describe("HeadlessInstallValidator (vehicle-client-pi pi-load-harness, non-gatin
|
|
|
145
155
|
|
|
146
156
|
describe("HeadlessInstallValidator (bug repro, packed-headlessinstallvalidator-never-installs-the): staged tarball's own declared dependencies are never installed before the load check", () => {
|
|
147
157
|
it("approves an otherwise-healthy package whose entry point needs its one declared file: dependency", async () => {
|
|
148
|
-
const dir = mkdtempSync(join(tmpdir(), "packed-install-validation-dep-fixture-"));
|
|
158
|
+
const dir = track(mkdtempSync(join(tmpdir(), "packed-install-validation-dep-fixture-")));
|
|
149
159
|
writePackageWithRealDependency(dir);
|
|
150
160
|
|
|
151
161
|
// Ground truth this isn't a broken fixture: a plain `bun install` in an
|
|
@@ -193,7 +203,7 @@ describe("ExecInstaller.install() -- refuses before ever spawning the real pi bi
|
|
|
193
203
|
// step to cwd into -- /bin/true always exits 0 regardless, proving both
|
|
194
204
|
// the real install spawn *and* the re-resolution spawn were reached (a
|
|
195
205
|
// refused install never gets this far to find out).
|
|
196
|
-
const piHome = mkdtempSync(join(tmpdir(), "packed-install-validation-pihome-"));
|
|
206
|
+
const piHome = track(mkdtempSync(join(tmpdir(), "packed-install-validation-pihome-")));
|
|
197
207
|
mkdirSync(join(piHome, "npm"), { recursive: true });
|
|
198
208
|
const installer = new ExecInstaller(
|
|
199
209
|
"/bin/true",
|
|
@@ -10,20 +10,40 @@
|
|
|
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";
|
|
18
|
+
import { createLogger } from "../src/shared/log.ts";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* A fake `pi` binary: always prints "Updated <source>" and exits 0 (matching
|
|
21
22
|
* real `pi`'s observed behavior for a no-op). When `rewrite` is given, it
|
|
22
23
|
* additionally overwrites that exact package's on-disk version -- letting a
|
|
23
24
|
* test simulate a genuine version change on demand. The rewrite target is
|
|
24
|
-
* baked into the script file itself (not an env var)
|
|
25
|
-
*
|
|
25
|
+
* baked into the script file itself (not an env var) rather than read from
|
|
26
|
+
* process.env at spawn time, keeping these fixtures independent of whichever
|
|
27
|
+
* way ExecInstaller happens to thread its own env through -- see the
|
|
28
|
+
* `run()`/`reresolveDependencyTree() thread env explicitly` describe block
|
|
29
|
+
* below for a dedicated test of that env-threading behavior itself, added
|
|
30
|
+
* after service/test/perf/multi-install.perf.test.ts caught it live: a
|
|
31
|
+
* runtime process.env mutation (redirecting Pi's home directory) silently
|
|
32
|
+
* never reached the spawned `pi`/`npm` children because Bun.spawn's own
|
|
33
|
+
* default env inheritance doesn't pick up a mutation made after this
|
|
34
|
+
* process's own startup snapshot -- only an explicit `env: process.env`
|
|
35
|
+
* option re-reads the current object.
|
|
26
36
|
*/
|
|
37
|
+
const roots: string[] = [];
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function track(dir: string): string {
|
|
43
|
+
roots.push(dir);
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
function writeFakePi(dir: string, rewrite?: { piHome: string; name: string; newVersion: string }): string {
|
|
28
48
|
const script = join(dir, "fake-pi");
|
|
29
49
|
const rewriteLine = rewrite
|
|
@@ -39,7 +59,7 @@ function writeFakePi(dir: string, rewrite?: { piHome: string; name: string; newV
|
|
|
39
59
|
}
|
|
40
60
|
|
|
41
61
|
function writePiHome(nodeModules: Record<string, string> = {}): string {
|
|
42
|
-
const dir = mkdtempSync(join(tmpdir(), "packed-exec-pihome-"));
|
|
62
|
+
const dir = track(mkdtempSync(join(tmpdir(), "packed-exec-pihome-")));
|
|
43
63
|
mkdirSync(join(dir, "npm"), { recursive: true });
|
|
44
64
|
for (const [name, version] of Object.entries(nodeModules)) {
|
|
45
65
|
const pkgDir = join(dir, "npm", "node_modules", name);
|
|
@@ -73,7 +93,7 @@ function writeFakeNpm(dir: string, logFile: string, rewrite?: { piHome: string;
|
|
|
73
93
|
|
|
74
94
|
describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguous exit-0 text", () => {
|
|
75
95
|
it("pinned source, version genuinely unchanged: alreadyUpToDate, reloadRequired false", async () => {
|
|
76
|
-
const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
|
|
96
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
77
97
|
const bin = writeFakePi(scriptDir);
|
|
78
98
|
const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
|
|
79
99
|
const piHome = writePiHome({ "@scope/pkg": "1.2.3" });
|
|
@@ -90,7 +110,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
|
|
|
90
110
|
});
|
|
91
111
|
|
|
92
112
|
it('unpinned source, already latest (pi still exits 0 and says "Updated"): alreadyUpToDate', async () => {
|
|
93
|
-
const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
|
|
113
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
94
114
|
const bin = writeFakePi(scriptDir);
|
|
95
115
|
const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
|
|
96
116
|
const piHome = writePiHome({ plain: "0.5.0" });
|
|
@@ -106,7 +126,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
|
|
|
106
126
|
});
|
|
107
127
|
|
|
108
128
|
it("unpinned source, a real version change happens: reloadRequired true", async () => {
|
|
109
|
-
const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
|
|
129
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
110
130
|
const piHome = writePiHome({ plain: "0.5.0" });
|
|
111
131
|
const bin = writeFakePi(scriptDir, { piHome, name: "plain", newVersion: "0.6.0" });
|
|
112
132
|
const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
|
|
@@ -122,7 +142,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
|
|
|
122
142
|
});
|
|
123
143
|
|
|
124
144
|
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-"));
|
|
145
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
126
146
|
const bin = writeFakePi(scriptDir);
|
|
127
147
|
const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
|
|
128
148
|
const piHome = writePiHome();
|
|
@@ -141,7 +161,7 @@ describe("ExecInstaller.update() — honest reloadRequired despite pi's ambiguou
|
|
|
141
161
|
|
|
142
162
|
describe("ExecInstaller — forces full dependency re-resolution, not just the target's own subtree", () => {
|
|
143
163
|
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-"));
|
|
164
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
145
165
|
// Simulates the confirmed live defect: after `pi update npm:@scope/leaf`,
|
|
146
166
|
// the leaf's own version bumps, but a root-level sibling
|
|
147
167
|
// (@scope/shared) that the freshly-updated leaf now needs a newer
|
|
@@ -170,7 +190,7 @@ describe("ExecInstaller — forces full dependency re-resolution, not just the t
|
|
|
170
190
|
});
|
|
171
191
|
|
|
172
192
|
it("install() also forces a full re-resolution after a successful pi install", async () => {
|
|
173
|
-
const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
|
|
193
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
174
194
|
const piHome = writePiHome({ "@scope/shared": "1.0.0" });
|
|
175
195
|
const bin = writeFakePi(scriptDir);
|
|
176
196
|
const npmLog = join(scriptDir, "npm.log");
|
|
@@ -185,7 +205,7 @@ describe("ExecInstaller — forces full dependency re-resolution, not just the t
|
|
|
185
205
|
});
|
|
186
206
|
|
|
187
207
|
it("surfaces a failed re-resolution instead of silently reporting success", async () => {
|
|
188
|
-
const scriptDir = mkdtempSync(join(tmpdir(), "packed-exec-bin-"));
|
|
208
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
189
209
|
const piHome = writePiHome({ plain: "0.5.0" });
|
|
190
210
|
const bin = writeFakePi(scriptDir, { piHome, name: "plain", newVersion: "0.6.0" });
|
|
191
211
|
const failingNpm = join(scriptDir, "fake-npm-fail");
|
|
@@ -196,3 +216,100 @@ describe("ExecInstaller — forces full dependency re-resolution, not just the t
|
|
|
196
216
|
await expect(installer.update("npm:plain")).rejects.toThrow(/npm install failed to re-resolve/);
|
|
197
217
|
});
|
|
198
218
|
});
|
|
219
|
+
|
|
220
|
+
describe("ExecInstaller — timing instrumentation (see service/test/perf/multi-install.perf.test.ts for a real multi-package measurement)", () => {
|
|
221
|
+
it("install() logs a validateMs/installMs/reresolveMs/totalMs breakdown, not just a pass/fail result", async () => {
|
|
222
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
223
|
+
const bin = writeFakePi(scriptDir);
|
|
224
|
+
const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
|
|
225
|
+
const piHome = writePiHome();
|
|
226
|
+
const lines: string[] = [];
|
|
227
|
+
const logger = createLogger("test", (line) => lines.push(line), "debug");
|
|
228
|
+
const installer = new ExecInstaller(bin, piHome, { validate: async (source) => ({ ok: true, source, extensions: [] }) }, npmBin, logger);
|
|
229
|
+
|
|
230
|
+
await installer.install("npm:plain");
|
|
231
|
+
|
|
232
|
+
const timing = lines.map((line) => JSON.parse(line)).find((entry) => entry.msg === "install timing");
|
|
233
|
+
expect(timing).toBeDefined();
|
|
234
|
+
expect(timing.source).toBe("npm:plain");
|
|
235
|
+
for (const field of ["validateMs", "installMs", "reresolveMs", "totalMs"]) {
|
|
236
|
+
expect(typeof timing[field]).toBe("number");
|
|
237
|
+
expect(timing[field]).toBeGreaterThanOrEqual(0);
|
|
238
|
+
}
|
|
239
|
+
// totalMs is the whole call's own wall clock, not just one phase re-labeled --
|
|
240
|
+
// bounded above by itself plus a small scheduling-noise allowance, never equal to
|
|
241
|
+
// a single phase alone once every phase is genuinely counted once.
|
|
242
|
+
expect(timing.totalMs).toBeGreaterThanOrEqual(timing.validateMs + timing.installMs + timing.reresolveMs - 1);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("update() logs an updateMs/reresolveMs/totalMs breakdown", async () => {
|
|
246
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-bin-")));
|
|
247
|
+
const bin = writeFakePi(scriptDir);
|
|
248
|
+
const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
|
|
249
|
+
const piHome = writePiHome({ plain: "0.5.0" });
|
|
250
|
+
const lines: string[] = [];
|
|
251
|
+
const logger = createLogger("test", (line) => lines.push(line), "debug");
|
|
252
|
+
const installer = new ExecInstaller(bin, piHome, undefined, npmBin, logger);
|
|
253
|
+
|
|
254
|
+
await installer.update("npm:plain");
|
|
255
|
+
|
|
256
|
+
const timing = lines.map((line) => JSON.parse(line)).find((entry) => entry.msg === "update timing");
|
|
257
|
+
expect(timing).toBeDefined();
|
|
258
|
+
expect(timing.source).toBe("npm:plain");
|
|
259
|
+
for (const field of ["updateMs", "reresolveMs", "totalMs"]) {
|
|
260
|
+
expect(typeof timing[field]).toBe("number");
|
|
261
|
+
expect(timing[field]).toBeGreaterThanOrEqual(0);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* A fake binary that dumps one specific env var's CURRENT value to `logFile` -- proves a
|
|
268
|
+
* process.env mutation made at runtime (after this test process's own startup, the exact shape
|
|
269
|
+
* of a caller redirecting Pi's home directory) actually reaches the spawned child, rather than
|
|
270
|
+
* whatever snapshot Bun.spawn's own default env inheritance captured earlier.
|
|
271
|
+
*/
|
|
272
|
+
function writeEnvDumpBinary(dir: string, name: string, varName: string, logFile: string): string {
|
|
273
|
+
const script = join(dir, name);
|
|
274
|
+
writeFileSync(script, ["#!/usr/bin/env bash", `printf '%s' "\$${varName}" > '${logFile}'`, "exit 0"].join("\n"));
|
|
275
|
+
chmodSync(script, 0o755);
|
|
276
|
+
return script;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
describe("ExecInstaller — run()/reresolveDependencyTree() thread the CURRENT process.env through explicitly", () => {
|
|
280
|
+
const MARKER = "PACKED_TEST_ENV_MARKER";
|
|
281
|
+
|
|
282
|
+
afterEach(() => {
|
|
283
|
+
delete process.env[MARKER];
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("install()'s pi spawn sees an env var set on process.env after this process already started", async () => {
|
|
287
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-env-")));
|
|
288
|
+
const piLog = join(scriptDir, "pi-env.log");
|
|
289
|
+
const bin = writeEnvDumpBinary(scriptDir, "fake-pi-env", MARKER, piLog);
|
|
290
|
+
const npmBin = writeFakeNpm(scriptDir, join(scriptDir, "npm.log"));
|
|
291
|
+
const piHome = writePiHome();
|
|
292
|
+
const installer = new ExecInstaller(bin, piHome, { validate: async (source) => ({ ok: true, source, extensions: [] }) }, npmBin);
|
|
293
|
+
|
|
294
|
+
// Mutated well after this test process's own startup -- exactly what redirecting Pi's home
|
|
295
|
+
// via PI_CODING_AGENT_DIR at runtime looks like from ExecInstaller's own point of view.
|
|
296
|
+
process.env[MARKER] = "set-after-startup";
|
|
297
|
+
await installer.install("npm:plain");
|
|
298
|
+
|
|
299
|
+
expect(readFileSync(piLog, "utf8")).toBe("set-after-startup");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("reresolveDependencyTree()'s npm spawn sees the same runtime env mutation", async () => {
|
|
303
|
+
const scriptDir = track(mkdtempSync(join(tmpdir(), "packed-exec-env-")));
|
|
304
|
+
const bin = writeFakePi(scriptDir);
|
|
305
|
+
const npmLog = join(scriptDir, "npm-env.log");
|
|
306
|
+
const npmBin = writeEnvDumpBinary(scriptDir, "fake-npm-env", MARKER, npmLog);
|
|
307
|
+
const piHome = writePiHome();
|
|
308
|
+
const installer = new ExecInstaller(bin, piHome, { validate: async (source) => ({ ok: true, source, extensions: [] }) }, npmBin);
|
|
309
|
+
|
|
310
|
+
process.env[MARKER] = "reresolve-sees-this-too";
|
|
311
|
+
await installer.install("npm:plain");
|
|
312
|
+
|
|
313
|
+
expect(readFileSync(npmLog, "utf8")).toBe("reresolve-sees-this-too");
|
|
314
|
+
});
|
|
315
|
+
});
|