@danypops/pi-packed 0.28.8 → 0.28.10
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
|
|
|
@@ -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;IA2FrF,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,6 +2,7 @@ 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;
|
|
6
7
|
const MAX_TEMP_DIRECTORY_SCAN = 100;
|
|
7
8
|
const RETAINED_VERSION_DIRECTORIES = 2;
|
|
@@ -64,6 +65,17 @@ function resolvedPackageJson(piHome: string, packageName: string): string {
|
|
|
64
65
|
return join(packageDirectory(piHome, packageName), "package.json");
|
|
65
66
|
}
|
|
66
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
|
+
|
|
67
79
|
export class PassThroughDaemonPackageMaterializer implements DaemonPackageMaterializer {
|
|
68
80
|
async materialize(piHome: string, source: string): Promise<MaterializedDaemonPackage> {
|
|
69
81
|
return { piHome, source, commit() {}, rollback() {} };
|
|
@@ -97,12 +109,12 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
97
109
|
if (!VERSION_DIRECTORY_RE.test(version)) throw new Error(`cannot isolate ${packageName}: installed package version is unsafe`);
|
|
98
110
|
|
|
99
111
|
const packageRoot = join(this.stateDirectory, "services", installKey(packageName));
|
|
100
|
-
const finalDirectory = join(packageRoot, version);
|
|
112
|
+
const finalDirectory = join(packageRoot, `${version}-f${INSTALL_FORMAT_VERSION}`);
|
|
101
113
|
const finalPiHome = isolatedPiHome(finalDirectory);
|
|
102
114
|
const finalPackageJson = resolvedPackageJson(finalPiHome, packageName);
|
|
103
115
|
mkdirSync(packageRoot, { recursive: true, mode: 0o700 });
|
|
104
116
|
this.pruneTemporaryDirectories(packageRoot);
|
|
105
|
-
if (readVersion(finalPackageJson) === version) {
|
|
117
|
+
if (readVersion(finalPackageJson) === version && hasCurrentInstallFormat(finalPiHome)) {
|
|
106
118
|
return {
|
|
107
119
|
piHome: finalPiHome,
|
|
108
120
|
source: `npm:${packageName}@${version}`,
|
|
@@ -111,14 +123,22 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
111
123
|
};
|
|
112
124
|
}
|
|
113
125
|
|
|
114
|
-
if (existsSync(finalDirectory)) rmSync(finalDirectory, { recursive: true, force: true });
|
|
115
126
|
const temporaryDirectory = join(packageRoot, `.${version}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
116
127
|
const temporaryPiHome = isolatedPiHome(temporaryDirectory);
|
|
117
128
|
const projectDirectory = join(temporaryPiHome, "npm");
|
|
118
129
|
mkdirSync(projectDirectory, { recursive: true, mode: 0o700 });
|
|
119
130
|
writeFileSync(
|
|
120
131
|
join(projectDirectory, "package.json"),
|
|
121
|
-
JSON.stringify(
|
|
132
|
+
JSON.stringify(
|
|
133
|
+
{
|
|
134
|
+
name: `packed-service-${installKey(packageName)}`,
|
|
135
|
+
private: true,
|
|
136
|
+
packedServiceInstallFormat: INSTALL_FORMAT_VERSION,
|
|
137
|
+
dependencies: { [packageName]: version },
|
|
138
|
+
},
|
|
139
|
+
null,
|
|
140
|
+
2,
|
|
141
|
+
),
|
|
122
142
|
);
|
|
123
143
|
|
|
124
144
|
let publishedByThisCall = false;
|
|
@@ -130,7 +150,6 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
130
150
|
"--ignore-scripts",
|
|
131
151
|
"--no-audit",
|
|
132
152
|
"--no-fund",
|
|
133
|
-
"--install-strategy=nested",
|
|
134
153
|
"--loglevel=error",
|
|
135
154
|
],
|
|
136
155
|
{ cwd: projectDirectory, stdout: "pipe", stderr: "pipe", env: process.env },
|
|
@@ -144,6 +163,10 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
144
163
|
throw new Error(`isolated npm install did not resolve ${packageName}@${version}`);
|
|
145
164
|
}
|
|
146
165
|
try {
|
|
166
|
+
// A damaged directory for this exact format is replaced only after the complete
|
|
167
|
+
// candidate exists. Format migrations use a different final path, so the service's
|
|
168
|
+
// currently registered executable remains present throughout installation.
|
|
169
|
+
if (existsSync(finalDirectory)) rmSync(finalDirectory, { recursive: true, force: true });
|
|
147
170
|
renameSync(temporaryDirectory, finalDirectory);
|
|
148
171
|
publishedByThisCall = true;
|
|
149
172
|
} catch (error) {
|
|
@@ -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,23 @@ describe("IsolatedDaemonPackageMaterializer", () => {
|
|
|
117
118
|
expect(existsSync(versionDirectory)).toBe(false);
|
|
118
119
|
});
|
|
119
120
|
|
|
121
|
+
it("uses format-versioned paths so a migration never removes the currently registered executable", async () => {
|
|
122
|
+
const root = temporaryRoot();
|
|
123
|
+
const piHome = join(root, "pi");
|
|
124
|
+
const stateDirectory = join(root, "state");
|
|
125
|
+
const legacyExecutable = join(stateDirectory, "services", "daemon", "1.0.0", "pi-home", "npm", "node_modules", "daemon", "cli.ts");
|
|
126
|
+
mkdirSync(join(legacyExecutable, ".."), { recursive: true });
|
|
127
|
+
writeFileSync(legacyExecutable, "// running legacy executable\n");
|
|
128
|
+
writeInstalledPackage(piHome, "daemon", "1.0.0");
|
|
129
|
+
const materializer = new IsolatedDaemonPackageMaterializer(stateDirectory, writeFakeNpm(root));
|
|
130
|
+
|
|
131
|
+
const installed = await materializer.materialize(piHome, "npm:daemon");
|
|
132
|
+
|
|
133
|
+
expect(existsSync(legacyExecutable)).toBe(true);
|
|
134
|
+
expect(installed.piHome).toContain("1.0.0-f1");
|
|
135
|
+
installed.commit();
|
|
136
|
+
});
|
|
137
|
+
|
|
120
138
|
it("retains only the active and immediately previous committed versions", async () => {
|
|
121
139
|
const root = temporaryRoot();
|
|
122
140
|
const piHome = join(root, "pi");
|
|
@@ -129,8 +147,8 @@ describe("IsolatedDaemonPackageMaterializer", () => {
|
|
|
129
147
|
}
|
|
130
148
|
|
|
131
149
|
expect(readdirSync(join(stateDirectory, "services", "daemon")).filter((name) => !name.startsWith(".")).sort()).toEqual([
|
|
132
|
-
"1.1.0",
|
|
133
|
-
"1.2.0",
|
|
150
|
+
"1.1.0-f1",
|
|
151
|
+
"1.2.0-f1",
|
|
134
152
|
]);
|
|
135
153
|
});
|
|
136
154
|
|