@danypops/pi-packed 0.27.3 → 0.27.4
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-packed",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.4",
|
|
4
4
|
"description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@danypops/vehicle-client": "^0.10.0",
|
|
34
34
|
"@danypops/vehicle-client-pi": "^0.40.1",
|
|
35
35
|
"@danypops/vehicle-core": "^0.15.0",
|
|
36
|
-
"@danypops/vehicle-server": "^0.24.
|
|
36
|
+
"@danypops/vehicle-server": "^0.24.2",
|
|
37
37
|
"jiti": "^2.7.0",
|
|
38
38
|
"malevich-tui-components": "^0.21.1",
|
|
39
39
|
"publint": "0.3.22",
|
|
@@ -127,6 +127,19 @@ export function daemonOptions(options: StartPackedDaemonOptions): StartDaemonOpt
|
|
|
127
127
|
intervalMs: envMs(ENV.RECONCILE_SECS, RECONCILE_INTERVAL_DEFAULT_MS),
|
|
128
128
|
run: async () => {
|
|
129
129
|
const result = await reconcileAllDaemonServices(piHome, undefined, daemonServiceInstaller);
|
|
130
|
+
// A package updated through a route pi-packed never sees at all (e.g. the generic,
|
|
131
|
+
// daemon-unaware `pi update --extension` / pkg_update path -- see
|
|
132
|
+
// pkg-update-never-restarts-vehicle-daemon) leaves this as the ONLY thing that ever
|
|
133
|
+
// notices and restarts the stale daemon. Logging only on failure made every silent
|
|
134
|
+
// success indistinguishable from "this never ran" -- there was no way to confirm a
|
|
135
|
+
// restart this task performed actually happened, short of checking the process's own
|
|
136
|
+
// PID by hand.
|
|
137
|
+
if (result.reconciled.some((entry) => entry.installed) || result.pruned.length > 0) {
|
|
138
|
+
logger.info("vehicle-reconcile applied changes", {
|
|
139
|
+
reconciled: result.reconciled.filter((entry) => entry.installed).map((entry) => entry.vehicleName),
|
|
140
|
+
pruned: result.pruned.map((entry) => entry.vehicleName),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
130
143
|
if (result.failed.length > 0) {
|
|
131
144
|
logger.warn("vehicle-reconcile completed with failures", {
|
|
132
145
|
reconciled: result.reconciled.length,
|
|
@@ -171,28 +184,23 @@ export function daemonOptions(options: StartPackedDaemonOptions): StartDaemonOpt
|
|
|
171
184
|
};
|
|
172
185
|
}
|
|
173
186
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
187
|
+
// startDaemon() itself now runs every maintenance task once immediately at startup (in
|
|
188
|
+
// addition to its own interval) -- see @danypops/vehicle-server's own daemon.ts. This used to
|
|
189
|
+
// be a bespoke wrapper here (runInitialMaintenance(), called after startDaemon() returned, or
|
|
190
|
+
// from serveMain()'s onListen) working around a bare setInterval() never firing until its full
|
|
191
|
+
// interval first elapsed; kept here would now double-run every task on every startup. Every
|
|
192
|
+
// OTHER vehicle-server-based daemon (lector, jittor, papyrus, pipes, tickets,
|
|
193
|
+
// web-spider-daemon) never had this workaround at all and now gets the same fix for free from
|
|
194
|
+
// the shared implementation, instead of needing to independently reinvent it.
|
|
182
195
|
export async function startPackedDaemon(options: StartPackedDaemonOptions = {}): Promise<RunningDaemon> {
|
|
183
|
-
|
|
184
|
-
const running = await startDaemon(configured);
|
|
185
|
-
runInitialMaintenance(configured.maintenanceTasks);
|
|
186
|
-
return running;
|
|
196
|
+
return startDaemon(daemonOptions(options));
|
|
187
197
|
}
|
|
188
198
|
|
|
189
199
|
export function serveMain(): void {
|
|
190
|
-
const configured = daemonOptions({});
|
|
191
200
|
runDaemonProcess({
|
|
192
|
-
...
|
|
201
|
+
...daemonOptions({}),
|
|
193
202
|
onListen: ({ host, port }) => {
|
|
194
203
|
logger.info("listening", { host, port });
|
|
195
|
-
runInitialMaintenance(configured.maintenanceTasks);
|
|
196
204
|
},
|
|
197
205
|
});
|
|
198
206
|
}
|
|
@@ -52,7 +52,17 @@ export const INDEX_OPERATION_TIMEOUT_MS = 10 * 60_000;
|
|
|
52
52
|
export const WATCH_INTERVAL_DEFAULT_MS = 30 * 60_000; // updates diff cadence
|
|
53
53
|
export const CATALOG_INTERVAL_DEFAULT_MS = 6 * 3_600_000; // full mirror TTL
|
|
54
54
|
export const INDEX_INTERVAL_DEFAULT_MS = 6 * 3_600_000; // static index regeneration TTL, same cadence as the catalog mirror it reads from
|
|
55
|
-
|
|
55
|
+
// Vehicle-service drift sweep cadence -- catches an out-of-band npm install/update a running
|
|
56
|
+
// daemon never picked up (e.g. a plain `pkg_update`/`pi update --extension`, which has no way to
|
|
57
|
+
// notify Armada/pi-packed at all -- see pkg-update-never-restarts-vehicle-daemon). Now that
|
|
58
|
+
// startDaemon() also runs every maintenance task once immediately at startup (see
|
|
59
|
+
// vehicle-server's own daemon.ts), a restart of pi-packed itself no longer waits out this
|
|
60
|
+
// interval at all -- this cadence now only bounds the OTHER case: an out-of-band update that
|
|
61
|
+
// lands while pi-packed's own daemon is already running and stays up. 30 minutes was tuned for
|
|
62
|
+
// a background safety net, not an interactive "I just updated something" workflow; 5 minutes
|
|
63
|
+
// keeps the same self-healing guarantee at a much more reasonable latency for a per-pass cost
|
|
64
|
+
// that's still just a handful of cheap native-service inspections for a fleet this size.
|
|
65
|
+
export const RECONCILE_INTERVAL_DEFAULT_MS = 5 * 60_000;
|
|
56
66
|
export const IDLE_BUDGET_DEFAULT_MS = 10 * 60_000; // on-demand self-exit
|
|
57
67
|
export const WATCHDOG_TICK_MS = 15_000;
|
|
58
68
|
|
|
@@ -226,6 +226,33 @@ describe("startPackedDaemon's own maintenance-task wiring (self-heals Vehicle dr
|
|
|
226
226
|
}
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Regression guard for the double-execution risk this file's own daemon.ts (startPackedDaemon/
|
|
231
|
+
* serveMain) used to carry: a bespoke runInitialMaintenance() wrapper called explicitly, on top
|
|
232
|
+
* of what startDaemon() (vehicle-server) itself now already does since it started running every
|
|
233
|
+
* maintenance task once immediately at startup. Removed entirely -- this proves it stayed
|
|
234
|
+
* removed, not just that a task runs at least once (the test above already covers that).
|
|
235
|
+
*/
|
|
236
|
+
it("runs each maintenance task exactly once at startup, never twice", async () => {
|
|
237
|
+
const piHome = fakePiHome([]);
|
|
238
|
+
const paths = fakePaths();
|
|
239
|
+
let runs = 0;
|
|
240
|
+
const task: MaintenanceTask = {
|
|
241
|
+
name: "probe-exactly-once",
|
|
242
|
+
intervalMs: RECONCILE_INTERVAL_DEFAULT_MS,
|
|
243
|
+
run: () => {
|
|
244
|
+
runs++;
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
const running = await startPackedDaemon({ paths, reg: registry, inst: installer, piHome, maintenanceTasks: [task] });
|
|
248
|
+
try {
|
|
249
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
250
|
+
expect(runs).toBe(1);
|
|
251
|
+
} finally {
|
|
252
|
+
await running.stop();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
229
256
|
it("does not reconcile Armada before the daemon has published its readiness handle", async () => {
|
|
230
257
|
const piHome = fakePiHome(["npm:@danypops/pi-packed", "npm:@danypops/probe"]);
|
|
231
258
|
const paths = fakePaths();
|