@danypops/pi-packed 0.26.0 → 0.27.0
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.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"typecheck": "bunx tsc --noEmit"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@danypops/armada": "^0.4.
|
|
30
|
+
"@danypops/armada": "^0.4.9",
|
|
31
31
|
"@danypops/packed": "^0.7.0",
|
|
32
32
|
"@danypops/pi-extension-harness": "^0.7.0",
|
|
33
33
|
"@danypops/vehicle-client": "^0.8.1",
|
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
* step isn't a real convention, so detection reads what's already true
|
|
26
26
|
* on disk instead of asking for one more declaration.
|
|
27
27
|
*/
|
|
28
|
+
import { spawn } from "node:child_process";
|
|
28
29
|
import { readdirSync, readFileSync } from "node:fs";
|
|
29
30
|
import { join } from "node:path";
|
|
31
|
+
import { fileURLToPath } from "node:url";
|
|
30
32
|
import { createVehicleRegistrar, type VehicleRegistrar, type VehicleRegistrationOutcome, type VehicleSpec } from "@danypops/armada";
|
|
31
33
|
import { resolveDaemonPaths } from "@danypops/vehicle-server/paths";
|
|
32
34
|
import {
|
|
@@ -439,6 +441,38 @@ const PACKED_PACKAGE_NAME = "@danypops/pi-packed";
|
|
|
439
441
|
* daemonOptions() -- vehicleName: PACKED_VEHICLE_NAME). One canonical name, not two. */
|
|
440
442
|
export const PACKED_VEHICLE_NAME = "pi-packed";
|
|
441
443
|
const SELF_REGISTRATION_REASON = "Packed cannot replace its own Armada service from inside the running daemon";
|
|
444
|
+
const SELF_RESTART_SCHEDULED_REASON = "self-restart scheduled via a detached armada CLI process";
|
|
445
|
+
/** Long enough for the HTTP response carrying this reason back to the caller to flush before the
|
|
446
|
+
* detached child's `armada restart` stops this very process. */
|
|
447
|
+
const SELF_RESTART_DELAY_MS = 500;
|
|
448
|
+
|
|
449
|
+
/** Fire-and-forget: schedule Packed's own Vehicle to restart from OUTSIDE this process. Packed
|
|
450
|
+
* can't safely stop+start itself in-process (the `stop` step would kill the very process handling
|
|
451
|
+
* this request before it could `start` again) -- so this spawns a detached `armada restart
|
|
452
|
+
* pi-packed` a beat later, after the response reporting "scheduled" has had time to flush, and lets
|
|
453
|
+
* it outlive this process via detached+unref. Swallows a resolution failure silently: worst case is
|
|
454
|
+
* the pre-existing behavior (a human restarts the service by hand), never a crash. */
|
|
455
|
+
export type SelfRestartScheduler = (spec: ServiceSpec) => void;
|
|
456
|
+
|
|
457
|
+
export const scheduleSelfRestart: SelfRestartScheduler = (_spec) => {
|
|
458
|
+
let cliPath: string;
|
|
459
|
+
try {
|
|
460
|
+
cliPath = fileURLToPath(import.meta.resolve("@danypops/armada/cli"));
|
|
461
|
+
} catch {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
setTimeout(() => {
|
|
465
|
+
try {
|
|
466
|
+
const child = spawn(process.execPath, [cliPath, "restart", PACKED_VEHICLE_NAME, "--json"], {
|
|
467
|
+
detached: true,
|
|
468
|
+
stdio: "ignore",
|
|
469
|
+
});
|
|
470
|
+
child.unref();
|
|
471
|
+
} catch {
|
|
472
|
+
/* best-effort -- a human can still restart the service by hand */
|
|
473
|
+
}
|
|
474
|
+
}, SELF_RESTART_DELAY_MS).unref();
|
|
475
|
+
};
|
|
442
476
|
|
|
443
477
|
/**
|
|
444
478
|
* Sweeps every installed Packed package (global scope, plus a project's own
|
|
@@ -557,7 +591,10 @@ export interface DaemonServiceInstaller {
|
|
|
557
591
|
* path/native controller are resolved once, not on every call.
|
|
558
592
|
*/
|
|
559
593
|
export class RealDaemonServiceInstaller implements DaemonServiceInstaller {
|
|
560
|
-
constructor(
|
|
594
|
+
constructor(
|
|
595
|
+
private readonly registrar: VehicleRegistrar = createVehicleRegistrar(),
|
|
596
|
+
private readonly selfRestart: SelfRestartScheduler = scheduleSelfRestart,
|
|
597
|
+
) {}
|
|
561
598
|
|
|
562
599
|
async listRegisteredVehicles(): Promise<readonly VehicleSpec[]> {
|
|
563
600
|
return this.registrar.listRegistered();
|
|
@@ -600,7 +637,8 @@ export class RealDaemonServiceInstaller implements DaemonServiceInstaller {
|
|
|
600
637
|
const resolved = resolveDaemonServiceSpec(piHome, source);
|
|
601
638
|
if (!resolved.ok) return resolved;
|
|
602
639
|
if (resolved.spec.name === PACKED_VEHICLE_NAME) {
|
|
603
|
-
|
|
640
|
+
this.selfRestart(resolved.spec);
|
|
641
|
+
return { ok: true, restarted: true, reason: SELF_RESTART_SCHEDULED_REASON, spec: resolved.spec };
|
|
604
642
|
}
|
|
605
643
|
if (!(await isVehicleServiceRegistered(resolved.spec.name, this.registrar))) {
|
|
606
644
|
return { ok: true, restarted: false, reason: `no persistent service is registered for ${resolved.spec.name}`, spec: resolved.spec };
|
|
@@ -9,8 +9,11 @@ import {
|
|
|
9
9
|
detectVehicleDaemonService,
|
|
10
10
|
listManagedPackages,
|
|
11
11
|
listUnconfiguredDaemonDependencies,
|
|
12
|
+
PACKED_VEHICLE_NAME,
|
|
12
13
|
RealDaemonServiceInstaller,
|
|
13
14
|
resolveDaemonServiceSpec,
|
|
15
|
+
scheduleSelfRestart,
|
|
16
|
+
type SelfRestartScheduler,
|
|
14
17
|
} from "../src/daemon/daemon-service.ts";
|
|
15
18
|
|
|
16
19
|
const roots: string[] = [];
|
|
@@ -470,6 +473,32 @@ describe("RealDaemonServiceInstaller -- Armada registration through the in-proce
|
|
|
470
473
|
}
|
|
471
474
|
});
|
|
472
475
|
|
|
476
|
+
it("restart() schedules an out-of-process self-restart for Packed's own Vehicle, never touching Armada in-process", async () => {
|
|
477
|
+
const piHome = fakePiHome();
|
|
478
|
+
writePackage(piHome, "@danypops/pi-packed", { binPath: "service/src/cli/cli.ts", args: ["serve"] });
|
|
479
|
+
const harness = await createArmadaTestHarness();
|
|
480
|
+
const scheduled: Array<{ name: string }> = [];
|
|
481
|
+
const selfRestart: SelfRestartScheduler = (spec) => {
|
|
482
|
+
scheduled.push({ name: spec.name });
|
|
483
|
+
};
|
|
484
|
+
try {
|
|
485
|
+
const installer = new RealDaemonServiceInstaller(harness.registrar, selfRestart);
|
|
486
|
+
const outcome = await installer.restart(piHome, "npm:@danypops/pi-packed");
|
|
487
|
+
|
|
488
|
+
expect(outcome).toMatchObject({ ok: true, restarted: true, spec: { name: "pi-packed" } });
|
|
489
|
+
expect(scheduled).toEqual([{ name: PACKED_VEHICLE_NAME }]);
|
|
490
|
+
// never asked Armada to stop/replace/start the very process running this test
|
|
491
|
+
expect(harness.events()).toEqual([]);
|
|
492
|
+
} finally {
|
|
493
|
+
await harness.dispose();
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("scheduleSelfRestart resolves armada's real CLI module -- proves the wiring is live, not just typechecked", () => {
|
|
498
|
+
expect(() => import.meta.resolve("@danypops/armada/cli")).not.toThrow();
|
|
499
|
+
expect(typeof scheduleSelfRestart).toBe("function");
|
|
500
|
+
});
|
|
501
|
+
|
|
473
502
|
it("install() reports a non-daemon package without touching Armada", async () => {
|
|
474
503
|
const piHome = fakePiHome();
|
|
475
504
|
writePackage(piHome, "some-pkg", undefined);
|