@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":"AAWA,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;AA4CD,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;IAgFrF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM5B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIrC,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,KAAK;CAkBb"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-packed",
3
- "version": "0.28.8",
3
+ "version": "0.28.10",
4
4
  "description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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({ name: `packed-service-${installKey(packageName)}`, private: true, dependencies: { [packageName]: version } }, null, 2),
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