@danypops/pi-packed 0.27.4 → 0.27.6
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 +3 -2
- package/service/src/adoption/check.ts +8 -2
- package/service/src/daemon/daemon-service.ts +15 -1
- package/service/src/daemon/daemon.ts +3 -2
- package/service/test/check.test.ts +33 -0
- package/service/test/cli.test.ts +2 -2
- package/service/test/daemon-maintenance.test.ts +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-packed",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.6",
|
|
4
4
|
"description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"@danypops/packed": "^0.7.0",
|
|
32
32
|
"@danypops/pi-extension-harness": "^0.7.0",
|
|
33
33
|
"@danypops/vehicle-client": "^0.10.0",
|
|
34
|
-
"@danypops/vehicle-client-pi": "^0.40.1",
|
|
35
34
|
"@danypops/vehicle-core": "^0.15.0",
|
|
36
35
|
"@danypops/vehicle-server": "^0.24.2",
|
|
37
36
|
"jiti": "^2.7.0",
|
|
@@ -41,9 +40,11 @@
|
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
42
|
"@danypops/pi-tui-harness": "^0.0.1",
|
|
43
|
+
"@danypops/vehicle-client-pi": "^0.41.3",
|
|
44
44
|
"@types/semver": "^7.7.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
+
"@danypops/vehicle-client-pi": "^0.41.3",
|
|
47
48
|
"@earendil-works/pi-coding-agent": "*",
|
|
48
49
|
"@earendil-works/pi-tui": "*",
|
|
49
50
|
"typebox": "*"
|
|
@@ -566,8 +566,14 @@ function dependencyCheck(context: Context): void {
|
|
|
566
566
|
path: file,
|
|
567
567
|
message: `${name} must be declared as peerDependencies[${name}] = *`,
|
|
568
568
|
});
|
|
569
|
-
} else if (dependencies[name] === undefined && optional[name] === undefined) {
|
|
570
|
-
|
|
569
|
+
} else if (dependencies[name] === undefined && optional[name] === undefined && peers[name] === undefined) {
|
|
570
|
+
// A real (non-"*") peerDependency range genuinely satisfies this -- npm's own
|
|
571
|
+
// documented mechanism for "a real runtime dependency the host is expected to
|
|
572
|
+
// already share a single instance of," distinct from CORE_PACKAGES' own stricter
|
|
573
|
+
// peers[name] === "*" contract (Pi's own always-exactly-one-host-version packages,
|
|
574
|
+
// checked separately above). A devDependency alone still doesn't count -- it
|
|
575
|
+
// guarantees nothing for a real consumer's own install.
|
|
576
|
+
const misplaced = dev[name] !== undefined ? "devDependency" : undefined;
|
|
571
577
|
context.add({
|
|
572
578
|
code: "RUNTIME_DEPENDENCY_MISSING",
|
|
573
579
|
severity: "error",
|
|
@@ -455,7 +455,13 @@ export function listManagedPackages(piHome: string, projectRoot?: string): Insta
|
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
export interface ReconcileAllResult {
|
|
458
|
-
|
|
458
|
+
/** `versionChanged` is true only when this pass's newly-resolved version genuinely differs
|
|
459
|
+
* from what Armada had registered before this sweep started -- `installed: true` alone means
|
|
460
|
+
* only "the register call itself succeeded", which is true on every healthy pass whether or
|
|
461
|
+
* not anything actually changed underneath (register()/reconcile() is a safe no-op against
|
|
462
|
+
* unchanged desired state). A caller wanting to know whether this pass genuinely restarted
|
|
463
|
+
* something (vs. just re-confirmed a healthy, unchanged fleet) must check this, not `installed`. */
|
|
464
|
+
reconciled: Array<{ packageName: string; vehicleName: string; installed: boolean; versionChanged: boolean; reason?: string }>;
|
|
459
465
|
skipped: number;
|
|
460
466
|
failed: Array<{ packageName: string; reason: string }>;
|
|
461
467
|
/** Every Vehicle unregistered because this sweep no longer discovers it at all -- see pruneStaleVehicles. */
|
|
@@ -555,6 +561,13 @@ export async function reconcileAllDaemonServices(
|
|
|
555
561
|
const failed: ReconcileAllResult["failed"] = [];
|
|
556
562
|
const seen = new Set<string>();
|
|
557
563
|
let skipped = 0;
|
|
564
|
+
// Snapshot BEFORE this sweep's own install() calls mutate Armada's registration -- the only
|
|
565
|
+
// way to tell "this pass's register() call genuinely changed something" from "register()
|
|
566
|
+
// succeeded against an already-current registration", since install()'s own ServiceInstallResult
|
|
567
|
+
// reports only the latter.
|
|
568
|
+
const previousVersions = new Map<string, string>(
|
|
569
|
+
(await installer.listRegisteredVehicles()).map((vehicle) => [String(vehicle.name), vehicle.version]),
|
|
570
|
+
);
|
|
558
571
|
for (const pkg of packages) {
|
|
559
572
|
// Re-registering the daemon from inside its own process makes Armada stop
|
|
560
573
|
// that process before registration can start the replacement.
|
|
@@ -577,6 +590,7 @@ export async function reconcileAllDaemonServices(
|
|
|
577
590
|
packageName: pkg.name,
|
|
578
591
|
vehicleName: resolved.spec.name,
|
|
579
592
|
installed: resolved.result.installed,
|
|
593
|
+
versionChanged: previousVersions.get(resolved.spec.name) !== resolved.spec.version,
|
|
580
594
|
...(resolved.result.installed ? {} : { reason: resolved.result.reason }),
|
|
581
595
|
});
|
|
582
596
|
}
|
|
@@ -134,9 +134,10 @@ export function daemonOptions(options: StartPackedDaemonOptions): StartDaemonOpt
|
|
|
134
134
|
// success indistinguishable from "this never ran" -- there was no way to confirm a
|
|
135
135
|
// restart this task performed actually happened, short of checking the process's own
|
|
136
136
|
// PID by hand.
|
|
137
|
-
|
|
137
|
+
const changed = result.reconciled.filter((entry) => entry.installed && entry.versionChanged);
|
|
138
|
+
if (changed.length > 0 || result.pruned.length > 0) {
|
|
138
139
|
logger.info("vehicle-reconcile applied changes", {
|
|
139
|
-
reconciled:
|
|
140
|
+
reconciled: changed.map((entry) => entry.vehicleName),
|
|
140
141
|
pruned: result.pruned.map((entry) => entry.vehicleName),
|
|
141
142
|
});
|
|
142
143
|
}
|
|
@@ -441,4 +441,37 @@ describe("dependencyCheck false-positive regressions (packed check)", () => {
|
|
|
441
441
|
const report = await checkPackage(root, { generic: false });
|
|
442
442
|
expect(codes(report.diagnostics)).not.toContain("RUNTIME_DEPENDENCY_MISSING");
|
|
443
443
|
});
|
|
444
|
+
|
|
445
|
+
it("a real (non-*) peerDependency range genuinely satisfies a shipped-code import -- npm's own documented mechanism for a shared-singleton runtime dependency, distinct from CORE_PACKAGES' own peers[name] === '*' contract", async () => {
|
|
446
|
+
const root = fixture(
|
|
447
|
+
{
|
|
448
|
+
...base,
|
|
449
|
+
files: ["extensions", "README.md", "LICENSE"],
|
|
450
|
+
peerDependencies: { ...base.peerDependencies, "@scope/shared-singleton": "^1.2.3" },
|
|
451
|
+
devDependencies: { "@scope/shared-singleton": "^1.2.3" },
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"extensions/index.ts": 'import { thing } from "@scope/shared-singleton";\nexport default function () { return thing; }\n',
|
|
455
|
+
"README.md": "# Example",
|
|
456
|
+
LICENSE: "MIT",
|
|
457
|
+
},
|
|
458
|
+
);
|
|
459
|
+
const report = await checkPackage(root, { generic: false });
|
|
460
|
+
expect(codes(report.diagnostics)).not.toContain("RUNTIME_DEPENDENCY_MISSING");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("still flags a shipped-code import declared only as a devDependency, even though a peerDependency-shaped exception now exists", async () => {
|
|
464
|
+
const root = fixture(
|
|
465
|
+
{ ...base, files: ["extensions", "README.md", "LICENSE"], devDependencies: { "leftover-dev-only": "1.0.0" } },
|
|
466
|
+
{
|
|
467
|
+
"extensions/index.ts": 'import thing from "leftover-dev-only";\nexport default function () { return thing; }\n',
|
|
468
|
+
"README.md": "# Example",
|
|
469
|
+
LICENSE: "MIT",
|
|
470
|
+
},
|
|
471
|
+
);
|
|
472
|
+
const report = await checkPackage(root, { generic: false });
|
|
473
|
+
expect(codes(report.diagnostics)).toContain("RUNTIME_DEPENDENCY_MISSING");
|
|
474
|
+
const diagnostic = report.diagnostics.find((d) => d.code === "RUNTIME_DEPENDENCY_MISSING")!;
|
|
475
|
+
expect(diagnostic.message).toContain("only a devDependency");
|
|
476
|
+
});
|
|
444
477
|
});
|
package/service/test/cli.test.ts
CHANGED
|
@@ -120,7 +120,7 @@ class FakeDaemonServiceInstaller {
|
|
|
120
120
|
return {
|
|
121
121
|
ok: true,
|
|
122
122
|
output: "reconciled 1 Vehicle(s), skipped 0 non-daemon package(s)",
|
|
123
|
-
reconciled: [{ packageName: "@danypops/probe", vehicleName: "probe", installed: true }],
|
|
123
|
+
reconciled: [{ packageName: "@danypops/probe", vehicleName: "probe", installed: true, versionChanged: true }],
|
|
124
124
|
skipped: 0,
|
|
125
125
|
failed: [],
|
|
126
126
|
pruned: [],
|
|
@@ -604,7 +604,7 @@ describe("CLI", () => {
|
|
|
604
604
|
expect(JSON.parse(json.out)).toEqual({
|
|
605
605
|
ok: true,
|
|
606
606
|
output: "reconciled 1 Vehicle(s), skipped 0 non-daemon package(s)",
|
|
607
|
-
reconciled: [{ packageName: "@danypops/probe", vehicleName: "probe", installed: true }],
|
|
607
|
+
reconciled: [{ packageName: "@danypops/probe", vehicleName: "probe", installed: true, versionChanged: true }],
|
|
608
608
|
skipped: 0,
|
|
609
609
|
failed: [],
|
|
610
610
|
pruned: [],
|
|
@@ -343,6 +343,46 @@ describe("startPackedDaemon's own maintenance-task wiring (self-heals Vehicle dr
|
|
|
343
343
|
});
|
|
344
344
|
});
|
|
345
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Real gap this closes: install()'s own ServiceInstallResult reports `installed: true`
|
|
348
|
+
* whenever register()/reconcile() SUCCEEDS -- which is true on every healthy pass whether or
|
|
349
|
+
* not anything actually changed underneath (register() is a safe no-op against unchanged
|
|
350
|
+
* desired state). A caller logging "applied changes" off `installed` alone would say so on
|
|
351
|
+
* literally every tick of a healthy, unchanged fleet -- see pkg-update-never-restarts-
|
|
352
|
+
* vehicle-daemon. versionChanged is the honest signal instead.
|
|
353
|
+
*/
|
|
354
|
+
describe("reconcileAllDaemonServices reports versionChanged honestly, not just installed", () => {
|
|
355
|
+
it("is true on a package's first-ever registration, false on an unchanged re-sweep, true again once its on-disk version actually moves", async () => {
|
|
356
|
+
const piHome = fakePiHome(["npm:@danypops/probe"]);
|
|
357
|
+
installMockDaemon(piHome, "@danypops/probe");
|
|
358
|
+
const harness = await createArmadaTestHarness();
|
|
359
|
+
try {
|
|
360
|
+
const installer = new RealDaemonServiceInstaller(harness.registrar);
|
|
361
|
+
|
|
362
|
+
const first = await reconcileAllDaemonServices(piHome, undefined, installer);
|
|
363
|
+
expect(first.reconciled).toEqual([
|
|
364
|
+
{ packageName: "@danypops/probe", vehicleName: "probe", installed: true, versionChanged: true },
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
const second = await reconcileAllDaemonServices(piHome, undefined, installer);
|
|
368
|
+
expect(second.reconciled).toEqual([
|
|
369
|
+
{ packageName: "@danypops/probe", vehicleName: "probe", installed: true, versionChanged: false },
|
|
370
|
+
]);
|
|
371
|
+
|
|
372
|
+
writeFileSync(
|
|
373
|
+
join(piHome, "npm", "node_modules", "@danypops", "probe", "package.json"),
|
|
374
|
+
JSON.stringify({ name: "@danypops/probe", version: "1.1.0", packed: { daemonService: { binPath: "cli.ts", args: ["serve"] } } }),
|
|
375
|
+
);
|
|
376
|
+
const third = await reconcileAllDaemonServices(piHome, undefined, installer);
|
|
377
|
+
expect(third.reconciled).toEqual([
|
|
378
|
+
{ packageName: "@danypops/probe", vehicleName: "probe", installed: true, versionChanged: true },
|
|
379
|
+
]);
|
|
380
|
+
} finally {
|
|
381
|
+
await harness.dispose();
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
346
386
|
describe("reconcileAllDaemonServices pruning (the-armada-registrar-jittor-web-spider-daemon-collision)", () => {
|
|
347
387
|
function stalePiHomeVehicle(piHome: string, name: string): VehicleSpec {
|
|
348
388
|
return {
|