@danypops/pi-packed 0.28.7 → 0.28.9
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/README.md
CHANGED
|
@@ -10,7 +10,7 @@ The extension connects to Packed's authenticated user daemon and starts the pack
|
|
|
10
10
|
|
|
11
11
|
## Isolated daemon services
|
|
12
12
|
|
|
13
|
-
Packed materializes every managed daemon into a private, versioned npm project under its state directory (`services/<package>/<version>/`). Each root has its own `package.json`, lockfile, and `node_modules`; Pi's shared global project, root-level `overrides`, hoisting, and unrelated package updates therefore cannot rewrite a daemon's dependency closure. Downloads still use npm's shared cache.
|
|
13
|
+
Packed materializes every managed daemon into a private, versioned npm project under its state directory (`services/<package>/<version>/`). Each root has its own `package.json`, lockfile, and `node_modules`; Pi's shared global project, root-level `overrides`, hoisting, and unrelated package updates therefore cannot rewrite a daemon's dependency closure. npm may hoist dependencies inside that one private root for ordinary package compatibility, but never across service roots. Downloads still use npm's shared cache.
|
|
14
14
|
|
|
15
15
|
A complete root is activated only after installation and manifest validation, using an atomic directory rename. Armada switches the persistent service to that root, and Packed retains the previous root for rollback while pruning older versions within a fixed bound. Removing the service removes its private roots. Pi extensions remain installed in Pi's own package project; only their daemon processes run from the isolated closure.
|
|
16
16
|
|
|
@@ -28,6 +28,7 @@ export declare class IsolatedDaemonPackageMaterializer implements DaemonPackageM
|
|
|
28
28
|
materialize(piHome: string, source: string): Promise<MaterializedDaemonPackage>;
|
|
29
29
|
remove(source: string): void;
|
|
30
30
|
ownsExecutable(path: string): boolean;
|
|
31
|
+
private pruneTemporaryDirectories;
|
|
31
32
|
private prune;
|
|
32
33
|
}
|
|
33
34
|
//# sourceMappingURL=isolated-service-install.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isolated-service-install.d.ts","sourceRoot":"","sources":["../../service/src/daemon/isolated-service-install.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"isolated-service-install.d.ts","sourceRoot":"","sources":["../../service/src/daemon/isolated-service-install.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,MAAM,IAAI,IAAI,CAAC;IACf,QAAQ,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,yBAAyB;IACzC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAChF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AAuDD,qBAAa,oCAAqC,YAAW,yBAAyB;IAC/E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAIrF,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE7B,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAGtC;AAED;;;;;;GAMG;AACH,qBAAa,iCAAkC,YAAW,yBAAyB;IAEjF,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,cAAc,EAAE,MAAM,EACtB,MAAM,SAAgE;IAGlF,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAwFrF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM5B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIrC,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,KAAK;CAkBb"}
|
package/package.json
CHANGED
|
@@ -2,8 +2,11 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, s
|
|
|
2
2
|
import { join, sep } from "node:path";
|
|
3
3
|
import { npmPackageName } from "../packages/installed.ts";
|
|
4
4
|
|
|
5
|
+
const INSTALL_FORMAT_VERSION = 1;
|
|
5
6
|
const MAX_VERSION_DIRECTORIES = 20;
|
|
7
|
+
const MAX_TEMP_DIRECTORY_SCAN = 100;
|
|
6
8
|
const RETAINED_VERSION_DIRECTORIES = 2;
|
|
9
|
+
const STALE_TEMP_DIRECTORY_MS = 5 * 60 * 1_000;
|
|
7
10
|
const MAX_INSTALL_OUTPUT_BYTES = 64 * 1024;
|
|
8
11
|
const VERSION_DIRECTORY_RE = /^[0-9A-Za-z][0-9A-Za-z.+_-]{0,127}$/;
|
|
9
12
|
|
|
@@ -62,6 +65,17 @@ function resolvedPackageJson(piHome: string, packageName: string): string {
|
|
|
62
65
|
return join(packageDirectory(piHome, packageName), "package.json");
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
function hasCurrentInstallFormat(piHome: string): boolean {
|
|
69
|
+
try {
|
|
70
|
+
const project = JSON.parse(readFileSync(join(piHome, "npm", "package.json"), "utf8")) as {
|
|
71
|
+
packedServiceInstallFormat?: unknown;
|
|
72
|
+
};
|
|
73
|
+
return project.packedServiceInstallFormat === INSTALL_FORMAT_VERSION;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
export class PassThroughDaemonPackageMaterializer implements DaemonPackageMaterializer {
|
|
66
80
|
async materialize(piHome: string, source: string): Promise<MaterializedDaemonPackage> {
|
|
67
81
|
return { piHome, source, commit() {}, rollback() {} };
|
|
@@ -98,7 +112,9 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
98
112
|
const finalDirectory = join(packageRoot, version);
|
|
99
113
|
const finalPiHome = isolatedPiHome(finalDirectory);
|
|
100
114
|
const finalPackageJson = resolvedPackageJson(finalPiHome, packageName);
|
|
101
|
-
|
|
115
|
+
mkdirSync(packageRoot, { recursive: true, mode: 0o700 });
|
|
116
|
+
this.pruneTemporaryDirectories(packageRoot);
|
|
117
|
+
if (readVersion(finalPackageJson) === version && hasCurrentInstallFormat(finalPiHome)) {
|
|
102
118
|
return {
|
|
103
119
|
piHome: finalPiHome,
|
|
104
120
|
source: `npm:${packageName}@${version}`,
|
|
@@ -107,7 +123,6 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
107
123
|
};
|
|
108
124
|
}
|
|
109
125
|
|
|
110
|
-
mkdirSync(packageRoot, { recursive: true, mode: 0o700 });
|
|
111
126
|
if (existsSync(finalDirectory)) rmSync(finalDirectory, { recursive: true, force: true });
|
|
112
127
|
const temporaryDirectory = join(packageRoot, `.${version}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
113
128
|
const temporaryPiHome = isolatedPiHome(temporaryDirectory);
|
|
@@ -115,7 +130,16 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
115
130
|
mkdirSync(projectDirectory, { recursive: true, mode: 0o700 });
|
|
116
131
|
writeFileSync(
|
|
117
132
|
join(projectDirectory, "package.json"),
|
|
118
|
-
JSON.stringify(
|
|
133
|
+
JSON.stringify(
|
|
134
|
+
{
|
|
135
|
+
name: `packed-service-${installKey(packageName)}`,
|
|
136
|
+
private: true,
|
|
137
|
+
packedServiceInstallFormat: INSTALL_FORMAT_VERSION,
|
|
138
|
+
dependencies: { [packageName]: version },
|
|
139
|
+
},
|
|
140
|
+
null,
|
|
141
|
+
2,
|
|
142
|
+
),
|
|
119
143
|
);
|
|
120
144
|
|
|
121
145
|
let publishedByThisCall = false;
|
|
@@ -127,7 +151,6 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
127
151
|
"--ignore-scripts",
|
|
128
152
|
"--no-audit",
|
|
129
153
|
"--no-fund",
|
|
130
|
-
"--install-strategy=nested",
|
|
131
154
|
"--loglevel=error",
|
|
132
155
|
],
|
|
133
156
|
{ cwd: projectDirectory, stdout: "pipe", stderr: "pipe", env: process.env },
|
|
@@ -176,6 +199,26 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
176
199
|
return path.startsWith(join(this.stateDirectory, "services") + sep);
|
|
177
200
|
}
|
|
178
201
|
|
|
202
|
+
private pruneTemporaryDirectories(packageRoot: string): void {
|
|
203
|
+
let names: string[];
|
|
204
|
+
try {
|
|
205
|
+
names = readdirSync(packageRoot)
|
|
206
|
+
.filter((name) => name.startsWith(".") && name.endsWith(".tmp"))
|
|
207
|
+
.slice(0, MAX_TEMP_DIRECTORY_SCAN);
|
|
208
|
+
} catch {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const staleBefore = Date.now() - STALE_TEMP_DIRECTORY_MS;
|
|
212
|
+
for (const name of names) {
|
|
213
|
+
const path = join(packageRoot, name);
|
|
214
|
+
try {
|
|
215
|
+
if (statSync(path).mtimeMs < staleBefore) rmSync(path, { recursive: true, force: true });
|
|
216
|
+
} catch {
|
|
217
|
+
// A concurrent materializer may have renamed or removed the candidate.
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
179
222
|
private prune(packageRoot: string, activeDirectory: string): void {
|
|
180
223
|
let directories: string[];
|
|
181
224
|
try {
|
|
@@ -188,7 +231,8 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
188
231
|
return;
|
|
189
232
|
}
|
|
190
233
|
directories.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs);
|
|
191
|
-
const
|
|
234
|
+
const prior = directories.filter((directory) => directory !== activeDirectory).slice(0, RETAINED_VERSION_DIRECTORIES - 1);
|
|
235
|
+
const retained = new Set([activeDirectory, ...prior]);
|
|
192
236
|
for (const directory of directories) {
|
|
193
237
|
if (!retained.has(directory)) rmSync(directory, { recursive: true, force: true });
|
|
194
238
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, utimesSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { IsolatedDaemonPackageMaterializer } from "../src/daemon/isolated-service-install.ts";
|
|
@@ -30,6 +30,7 @@ const fs = require("node:fs");
|
|
|
30
30
|
const path = require("node:path");
|
|
31
31
|
const project = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"));
|
|
32
32
|
if (project.overrides) throw new Error("host overrides leaked into isolated project");
|
|
33
|
+
if (project.packedServiceInstallFormat !== 1) throw new Error("missing isolated install format marker");
|
|
33
34
|
const [name, version] = Object.entries(project.dependencies)[0];
|
|
34
35
|
const vehicleVersions = ${JSON.stringify(vehicleVersions)};
|
|
35
36
|
const vehicleVersion = typeof vehicleVersions === "string" ? vehicleVersions : vehicleVersions[name];
|
|
@@ -117,6 +118,44 @@ describe("IsolatedDaemonPackageMaterializer", () => {
|
|
|
117
118
|
expect(existsSync(versionDirectory)).toBe(false);
|
|
118
119
|
});
|
|
119
120
|
|
|
121
|
+
it("retains only the active and immediately previous committed versions", async () => {
|
|
122
|
+
const root = temporaryRoot();
|
|
123
|
+
const piHome = join(root, "pi");
|
|
124
|
+
const stateDirectory = join(root, "state");
|
|
125
|
+
const materializer = new IsolatedDaemonPackageMaterializer(stateDirectory, writeFakeNpm(root));
|
|
126
|
+
for (const version of ["1.0.0", "1.1.0", "1.2.0"]) {
|
|
127
|
+
writeInstalledPackage(piHome, "daemon", version);
|
|
128
|
+
const installed = await materializer.materialize(piHome, "npm:daemon");
|
|
129
|
+
installed.commit();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
expect(readdirSync(join(stateDirectory, "services", "daemon")).filter((name) => !name.startsWith(".")).sort()).toEqual([
|
|
133
|
+
"1.1.0",
|
|
134
|
+
"1.2.0",
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("cleans bounded stale crash candidates without touching a fresh concurrent candidate", async () => {
|
|
139
|
+
const root = temporaryRoot();
|
|
140
|
+
const piHome = join(root, "pi");
|
|
141
|
+
const stateDirectory = join(root, "state");
|
|
142
|
+
const packageRoot = join(stateDirectory, "services", "daemon");
|
|
143
|
+
const stale = join(packageRoot, ".stale.tmp");
|
|
144
|
+
const fresh = join(packageRoot, ".fresh.tmp");
|
|
145
|
+
mkdirSync(stale, { recursive: true });
|
|
146
|
+
mkdirSync(fresh, { recursive: true });
|
|
147
|
+
const old = new Date(Date.now() - 10 * 60 * 1_000);
|
|
148
|
+
utimesSync(stale, old, old);
|
|
149
|
+
writeInstalledPackage(piHome, "daemon", "1.0.0");
|
|
150
|
+
const materializer = new IsolatedDaemonPackageMaterializer(stateDirectory, writeFakeNpm(root));
|
|
151
|
+
|
|
152
|
+
const installed = await materializer.materialize(piHome, "npm:daemon");
|
|
153
|
+
|
|
154
|
+
expect(existsSync(stale)).toBe(false);
|
|
155
|
+
expect(existsSync(fresh)).toBe(true);
|
|
156
|
+
installed.rollback();
|
|
157
|
+
});
|
|
158
|
+
|
|
120
159
|
it("rejects an unsafe installed version before creating a service directory", async () => {
|
|
121
160
|
const root = temporaryRoot();
|
|
122
161
|
const piHome = join(root, "pi");
|