@danypops/pi-packed 0.22.3 → 0.22.5
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/package.json
CHANGED
|
@@ -146,6 +146,28 @@ export function detectVehicleDaemonService(
|
|
|
146
146
|
for (const depDir of candidateDirs) {
|
|
147
147
|
const dep = readPackageJson(depDir);
|
|
148
148
|
if (!dep) continue;
|
|
149
|
+
// An explicit manifest on the dependency itself wins over convention detection here too --
|
|
150
|
+
// mirrors resolveDaemonServiceSpec's own explicit-manifest-first behavior for a directly
|
|
151
|
+
// installed package. Without this, a dependency's own correct, explicit
|
|
152
|
+
// handleFilename/name/etc. was silently discarded in favor of a guess whenever the daemon
|
|
153
|
+
// was discovered one level down instead of installed directly by name -- exactly the
|
|
154
|
+
// common case (a Pi extension like pi-papyrus has no daemon of its own). Confirmed live:
|
|
155
|
+
// papyrus's real handle file is "vehicle-handle.json", not the "daemon.json" convention
|
|
156
|
+
// guess, and papyrus is only ever discovered this way, never installed directly by name.
|
|
157
|
+
const depManifest = dep.packed?.daemonService;
|
|
158
|
+
if (depManifest && typeof depManifest.binPath === "string" && depManifest.binPath.length > 0 && dep.version) {
|
|
159
|
+
return {
|
|
160
|
+
binPath: join(depDir, depManifest.binPath),
|
|
161
|
+
args: depManifest.args,
|
|
162
|
+
name: depManifest.name ?? unscopedName(dep.name ?? depName),
|
|
163
|
+
displayName: depManifest.displayName,
|
|
164
|
+
handleFilename: depManifest.handleFilename,
|
|
165
|
+
workingDirectory: depManifest.workingDirectory,
|
|
166
|
+
restartOnFailure: depManifest.restartOnFailure,
|
|
167
|
+
restartSec: depManifest.restartSec,
|
|
168
|
+
version: dep.version,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
149
171
|
const depBin = firstBinPath(dep);
|
|
150
172
|
if (depBin && dependsOnVehicle(dep) && dep.version) {
|
|
151
173
|
return { binPath: join(depDir, depBin), args: ["serve"], name: unscopedName(dep.name ?? depName), version: dep.version };
|
|
@@ -9,12 +9,18 @@ import {
|
|
|
9
9
|
} from "@danypops/vehicle-server/daemon";
|
|
10
10
|
import { ensureAuthToken } from "@danypops/vehicle-server/paths";
|
|
11
11
|
import { captureLoadedModules, checkModuleFreshnessAll, ownRuntimeDependencyNames } from "../adoption/module-freshness.ts";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
type DaemonServiceInstaller,
|
|
14
|
+
listManagedPackages,
|
|
15
|
+
PACKED_VEHICLE_NAME,
|
|
16
|
+
RealDaemonServiceInstaller,
|
|
17
|
+
reconcileAllDaemonServices,
|
|
18
|
+
} from "./daemon-service.ts";
|
|
13
19
|
import { generateIndex, indexPath, indexStatus } from "../index/build-index.ts";
|
|
14
20
|
import { catalogStatus, syncCatalog } from "../packages/catalog.ts";
|
|
15
21
|
import { latestVersion, openDb } from "../packages/db.ts";
|
|
16
22
|
import { ExecInstaller } from "../packages/install.ts";
|
|
17
|
-
import { defaultPiHome
|
|
23
|
+
import { defaultPiHome } from "../packages/installed.ts";
|
|
18
24
|
import type { Installer, Registry } from "../packages/package.ts";
|
|
19
25
|
import { HttpRegistry } from "../registry/registry.ts";
|
|
20
26
|
import {
|
|
@@ -87,7 +93,10 @@ export function daemonOptions(options: StartPackedDaemonOptions): StartDaemonOpt
|
|
|
87
93
|
name: "package-update-check",
|
|
88
94
|
intervalMs: envMs(ENV.WATCH_SECS, WATCH_INTERVAL_DEFAULT_MS),
|
|
89
95
|
run: async () => {
|
|
90
|
-
|
|
96
|
+
// listManagedPackages(), not readInstalledPackages() -- also catches a physically
|
|
97
|
+
// installed daemon-dependency that isn't itself pi:-configured (e.g. papyrus via
|
|
98
|
+
// pi-papyrus). readInstalledPackages() alone silently missed every such stale package.
|
|
99
|
+
const updates = checkUpdates((name) => latestVersion(database, name), listManagedPackages(piHome));
|
|
91
100
|
await saveUpdates(paths.stateDirectory, { checkedAt: new Date().toISOString(), updates });
|
|
92
101
|
},
|
|
93
102
|
},
|
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
RealDaemonServiceInstaller,
|
|
12
12
|
reconcileAllDaemonServices,
|
|
13
13
|
} from "../src/daemon/daemon-service.ts";
|
|
14
|
+
import { openDb, replaceAll } from "../src/packages/db.ts";
|
|
14
15
|
import type { Installer, Registry } from "../src/packages/package.ts";
|
|
15
16
|
import { RECONCILE_INTERVAL_DEFAULT_MS } from "../src/shared/constants.ts";
|
|
16
17
|
import { resolvePackedPaths } from "../src/shared/paths.ts";
|
|
18
|
+
import { loadUpdates } from "../src/daemon/watcher.ts";
|
|
17
19
|
|
|
18
20
|
const roots: string[] = [];
|
|
19
21
|
afterEach(() => {
|
|
@@ -57,6 +59,24 @@ function installMockDaemon(piHome: string, name: string): void {
|
|
|
57
59
|
writeFileSync(join(directory, "cli.ts"), "// Mock Vehicle entry point; Armada's test controller never executes it.\n");
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/** A Vehicle-shaped daemon-dependency (real bin + vehicle-server dependency), not declared in
|
|
63
|
+
* settings.json's packages[] -- e.g. papyrus, resolved only via pi-papyrus. */
|
|
64
|
+
function installUnconfiguredDaemonDependency(piHome: string, name: string, version: string): void {
|
|
65
|
+
const directory = join(piHome, "npm", "node_modules", name);
|
|
66
|
+
mkdirSync(directory, { recursive: true });
|
|
67
|
+
writeFileSync(
|
|
68
|
+
join(directory, "package.json"),
|
|
69
|
+
JSON.stringify({ name, version, bin: { probe: "cli.ts" }, dependencies: { "@danypops/vehicle-server": "^0.1.0" } }),
|
|
70
|
+
);
|
|
71
|
+
writeFileSync(join(directory, "cli.ts"), "// Mock Vehicle entry point.\n");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function installConfiguredExtension(piHome: string, name: string, version: string): void {
|
|
75
|
+
const directory = join(piHome, "npm", "node_modules", name);
|
|
76
|
+
mkdirSync(directory, { recursive: true });
|
|
77
|
+
writeFileSync(join(directory, "package.json"), JSON.stringify({ name, version }));
|
|
78
|
+
}
|
|
79
|
+
|
|
60
80
|
const registry: Registry = {
|
|
61
81
|
async search() {
|
|
62
82
|
return { total: 0, results: [] };
|
|
@@ -252,4 +272,40 @@ describe("startPackedDaemon's own maintenance-task wiring (self-heals Vehicle dr
|
|
|
252
272
|
await harness.dispose();
|
|
253
273
|
}
|
|
254
274
|
});
|
|
275
|
+
|
|
276
|
+
describe("package-update-check (feeds package.updates / pkg_updates)", () => {
|
|
277
|
+
it("reports a stale pi:-configured extension", async () => {
|
|
278
|
+
const piHome = fakePiHome(["npm:@danypops/probe-ext"]);
|
|
279
|
+
installConfiguredExtension(piHome, "@danypops/probe-ext", "1.0.0");
|
|
280
|
+
const paths = fakePaths();
|
|
281
|
+
const db = openDb(paths.database);
|
|
282
|
+
replaceAll(db, [{ name: "@danypops/probe-ext", version: "2.0.0" }], "test");
|
|
283
|
+
db.close();
|
|
284
|
+
|
|
285
|
+
const options = daemonOptions({ paths, reg: registry, inst: installer, piHome });
|
|
286
|
+
const task = options.maintenanceTasks?.find((t) => t.name === "package-update-check");
|
|
287
|
+
expect(task).toBeDefined();
|
|
288
|
+
await task?.run();
|
|
289
|
+
|
|
290
|
+
const snapshot = await loadUpdates(paths.stateDirectory);
|
|
291
|
+
expect(snapshot?.updates.map((u) => u.name)).toContain("@danypops/probe-ext");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("also reports a stale daemon-dependency package (e.g. papyrus via pi-papyrus)", async () => {
|
|
295
|
+
const piHome = fakePiHome([]); // deliberately NOT declaring probe-daemon in settings.json
|
|
296
|
+
installUnconfiguredDaemonDependency(piHome, "@danypops/probe-daemon", "1.0.0");
|
|
297
|
+
const paths = fakePaths();
|
|
298
|
+
const db = openDb(paths.database);
|
|
299
|
+
replaceAll(db, [{ name: "@danypops/probe-daemon", version: "9.9.9" }], "test");
|
|
300
|
+
db.close();
|
|
301
|
+
|
|
302
|
+
const options = daemonOptions({ paths, reg: registry, inst: installer, piHome });
|
|
303
|
+
const task = options.maintenanceTasks?.find((t) => t.name === "package-update-check");
|
|
304
|
+
expect(task).toBeDefined();
|
|
305
|
+
await task?.run();
|
|
306
|
+
|
|
307
|
+
const snapshot = await loadUpdates(paths.stateDirectory);
|
|
308
|
+
expect(snapshot?.updates.map((u) => u.name)).toContain("@danypops/probe-daemon");
|
|
309
|
+
});
|
|
310
|
+
});
|
|
255
311
|
});
|
|
@@ -185,6 +185,29 @@ describe("resolveDaemonServiceSpec", () => {
|
|
|
185
185
|
expect(result.spec.name).toBe("papyrus");
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
it("honors a nested dependency's own explicit packed.daemonService manifest, not just bin+dependency convention -- confirmed live: papyrus's real handle file is 'vehicle-handle.json', not the 'daemon.json' convention guess, and papyrus is only ever discovered this way (one level into pi-papyrus's own dependencies), never installed directly by name", () => {
|
|
189
|
+
const piHome = fakePiHome();
|
|
190
|
+
const extDir = join(piHome, "npm", "node_modules", "@danypops/pi-papyrus");
|
|
191
|
+
writeRawPackage(extDir, { name: "@danypops/pi-papyrus", version: "1.0.0", dependencies: { "@danypops/papyrus": "^0.54.0" } });
|
|
192
|
+
const depDir = join(extDir, "node_modules", "@danypops/papyrus");
|
|
193
|
+
writeRawPackage(depDir, {
|
|
194
|
+
name: "@danypops/papyrus",
|
|
195
|
+
version: "0.54.0",
|
|
196
|
+
bin: { papyrus: "src/cli.ts" },
|
|
197
|
+
dependencies: { "@danypops/vehicle-server": "^0.24.0" },
|
|
198
|
+
packed: { daemonService: { binPath: "src/cli.ts", args: ["serve"], handleFilename: "vehicle-handle.json", restartOnFailure: true, restartSec: 2 } },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const result = resolveDaemonServiceSpec(piHome, "npm:@danypops/pi-papyrus");
|
|
202
|
+
expect(result.ok).toBe(true);
|
|
203
|
+
if (!result.ok) throw new Error("expected ok");
|
|
204
|
+
expect(result.spec.binPath).toBe(join(depDir, "src/cli.ts"));
|
|
205
|
+
expect(result.spec.name).toBe("papyrus");
|
|
206
|
+
expect(result.spec.handlePath.endsWith("vehicle-handle.json")).toBe(true);
|
|
207
|
+
expect(result.spec.restartOnFailure).toBe(true);
|
|
208
|
+
expect(result.spec.restartSec).toBe(2);
|
|
209
|
+
});
|
|
210
|
+
|
|
188
211
|
it("also detects the legacy @danypops/daemon-kit dependency name, not just vehicle-server", () => {
|
|
189
212
|
const piHome = fakePiHome();
|
|
190
213
|
const dir = join(piHome, "npm", "node_modules", "@danypops/web-spider-daemon");
|