@danypops/pi-packed 0.28.7 → 0.28.8
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.
|
@@ -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":"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"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,9 @@ import { join, sep } from "node:path";
|
|
|
3
3
|
import { npmPackageName } from "../packages/installed.ts";
|
|
4
4
|
|
|
5
5
|
const MAX_VERSION_DIRECTORIES = 20;
|
|
6
|
+
const MAX_TEMP_DIRECTORY_SCAN = 100;
|
|
6
7
|
const RETAINED_VERSION_DIRECTORIES = 2;
|
|
8
|
+
const STALE_TEMP_DIRECTORY_MS = 5 * 60 * 1_000;
|
|
7
9
|
const MAX_INSTALL_OUTPUT_BYTES = 64 * 1024;
|
|
8
10
|
const VERSION_DIRECTORY_RE = /^[0-9A-Za-z][0-9A-Za-z.+_-]{0,127}$/;
|
|
9
11
|
|
|
@@ -98,6 +100,8 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
98
100
|
const finalDirectory = join(packageRoot, version);
|
|
99
101
|
const finalPiHome = isolatedPiHome(finalDirectory);
|
|
100
102
|
const finalPackageJson = resolvedPackageJson(finalPiHome, packageName);
|
|
103
|
+
mkdirSync(packageRoot, { recursive: true, mode: 0o700 });
|
|
104
|
+
this.pruneTemporaryDirectories(packageRoot);
|
|
101
105
|
if (readVersion(finalPackageJson) === version) {
|
|
102
106
|
return {
|
|
103
107
|
piHome: finalPiHome,
|
|
@@ -107,7 +111,6 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
107
111
|
};
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
mkdirSync(packageRoot, { recursive: true, mode: 0o700 });
|
|
111
114
|
if (existsSync(finalDirectory)) rmSync(finalDirectory, { recursive: true, force: true });
|
|
112
115
|
const temporaryDirectory = join(packageRoot, `.${version}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
113
116
|
const temporaryPiHome = isolatedPiHome(temporaryDirectory);
|
|
@@ -176,6 +179,26 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
176
179
|
return path.startsWith(join(this.stateDirectory, "services") + sep);
|
|
177
180
|
}
|
|
178
181
|
|
|
182
|
+
private pruneTemporaryDirectories(packageRoot: string): void {
|
|
183
|
+
let names: string[];
|
|
184
|
+
try {
|
|
185
|
+
names = readdirSync(packageRoot)
|
|
186
|
+
.filter((name) => name.startsWith(".") && name.endsWith(".tmp"))
|
|
187
|
+
.slice(0, MAX_TEMP_DIRECTORY_SCAN);
|
|
188
|
+
} catch {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const staleBefore = Date.now() - STALE_TEMP_DIRECTORY_MS;
|
|
192
|
+
for (const name of names) {
|
|
193
|
+
const path = join(packageRoot, name);
|
|
194
|
+
try {
|
|
195
|
+
if (statSync(path).mtimeMs < staleBefore) rmSync(path, { recursive: true, force: true });
|
|
196
|
+
} catch {
|
|
197
|
+
// A concurrent materializer may have renamed or removed the candidate.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
179
202
|
private prune(packageRoot: string, activeDirectory: string): void {
|
|
180
203
|
let directories: string[];
|
|
181
204
|
try {
|
|
@@ -188,7 +211,8 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
188
211
|
return;
|
|
189
212
|
}
|
|
190
213
|
directories.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs);
|
|
191
|
-
const
|
|
214
|
+
const prior = directories.filter((directory) => directory !== activeDirectory).slice(0, RETAINED_VERSION_DIRECTORIES - 1);
|
|
215
|
+
const retained = new Set([activeDirectory, ...prior]);
|
|
192
216
|
for (const directory of directories) {
|
|
193
217
|
if (!retained.has(directory)) rmSync(directory, { recursive: true, force: true });
|
|
194
218
|
}
|
|
@@ -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";
|
|
@@ -117,6 +117,44 @@ describe("IsolatedDaemonPackageMaterializer", () => {
|
|
|
117
117
|
expect(existsSync(versionDirectory)).toBe(false);
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
it("retains only the active and immediately previous committed versions", async () => {
|
|
121
|
+
const root = temporaryRoot();
|
|
122
|
+
const piHome = join(root, "pi");
|
|
123
|
+
const stateDirectory = join(root, "state");
|
|
124
|
+
const materializer = new IsolatedDaemonPackageMaterializer(stateDirectory, writeFakeNpm(root));
|
|
125
|
+
for (const version of ["1.0.0", "1.1.0", "1.2.0"]) {
|
|
126
|
+
writeInstalledPackage(piHome, "daemon", version);
|
|
127
|
+
const installed = await materializer.materialize(piHome, "npm:daemon");
|
|
128
|
+
installed.commit();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
expect(readdirSync(join(stateDirectory, "services", "daemon")).filter((name) => !name.startsWith(".")).sort()).toEqual([
|
|
132
|
+
"1.1.0",
|
|
133
|
+
"1.2.0",
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("cleans bounded stale crash candidates without touching a fresh concurrent candidate", async () => {
|
|
138
|
+
const root = temporaryRoot();
|
|
139
|
+
const piHome = join(root, "pi");
|
|
140
|
+
const stateDirectory = join(root, "state");
|
|
141
|
+
const packageRoot = join(stateDirectory, "services", "daemon");
|
|
142
|
+
const stale = join(packageRoot, ".stale.tmp");
|
|
143
|
+
const fresh = join(packageRoot, ".fresh.tmp");
|
|
144
|
+
mkdirSync(stale, { recursive: true });
|
|
145
|
+
mkdirSync(fresh, { recursive: true });
|
|
146
|
+
const old = new Date(Date.now() - 10 * 60 * 1_000);
|
|
147
|
+
utimesSync(stale, old, old);
|
|
148
|
+
writeInstalledPackage(piHome, "daemon", "1.0.0");
|
|
149
|
+
const materializer = new IsolatedDaemonPackageMaterializer(stateDirectory, writeFakeNpm(root));
|
|
150
|
+
|
|
151
|
+
const installed = await materializer.materialize(piHome, "npm:daemon");
|
|
152
|
+
|
|
153
|
+
expect(existsSync(stale)).toBe(false);
|
|
154
|
+
expect(existsSync(fresh)).toBe(true);
|
|
155
|
+
installed.rollback();
|
|
156
|
+
});
|
|
157
|
+
|
|
120
158
|
it("rejects an unsafe installed version before creating a service directory", async () => {
|
|
121
159
|
const root = temporaryRoot();
|
|
122
160
|
const piHome = join(root, "pi");
|