@danypops/pi-packed 0.27.10 → 0.27.12
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.12",
|
|
4
4
|
"description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@danypops/pi-tui-harness": "^0.0.1",
|
|
43
43
|
"@danypops/vehicle-client-pi": "^0.43.1",
|
|
44
|
+
"@danypops/vehicle-conformance": "^0.4.0",
|
|
44
45
|
"@types/semver": "^7.7.1"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* kind of collision at actual startup, one package at a time; doctor
|
|
7
7
|
* answers the same question proactively, before pi ever runs.
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
9
|
+
import { existsSync, statSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { createNodeServiceInstallDeps } from "@danypops/vehicle-server/service";
|
|
12
12
|
import { listPackageResources, type PackageResources, resolveInstalledDir } from "../packages/resources.ts";
|
|
@@ -134,6 +134,13 @@ export async function runDoctor(piHome: string, projectRoot?: string, options: S
|
|
|
134
134
|
const serviceReport = checkServiceUnitPaths(piHome, projectRoot, {
|
|
135
135
|
...createNodeServiceInstallDeps(),
|
|
136
136
|
fileExists: existsSync,
|
|
137
|
+
getMtimeMs: (path) => {
|
|
138
|
+
try {
|
|
139
|
+
return statSync(path).mtimeMs;
|
|
140
|
+
} catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
137
144
|
});
|
|
138
145
|
|
|
139
146
|
return {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import type { ServiceInstallDeps } from "@danypops/vehicle-server/service";
|
|
2
3
|
import { readInstalledPackagesAcrossScopes } from "../packages/installed.ts";
|
|
3
4
|
import { resolveDaemonServiceSpec } from "../daemon/daemon-service.ts";
|
|
4
5
|
|
|
5
6
|
export interface ServiceUnitDiagnostic {
|
|
6
|
-
code: "SERVICE_EXEC_PATH_MISSING" | "SERVICE_NOT_RUNNING" | "SERVICE_STATUS_UNAVAILABLE";
|
|
7
|
+
code: "SERVICE_EXEC_PATH_MISSING" | "SERVICE_NOT_RUNNING" | "SERVICE_STATUS_UNAVAILABLE" | "SERVICE_STALE_CODE";
|
|
7
8
|
severity: "error" | "warning";
|
|
8
9
|
package: string;
|
|
9
10
|
unitName: string;
|
|
@@ -18,10 +19,23 @@ export interface ServiceDoctorReport {
|
|
|
18
19
|
|
|
19
20
|
export interface ServiceDoctorDeps extends ServiceInstallDeps {
|
|
20
21
|
fileExists(path: string): boolean;
|
|
22
|
+
/** Undefined (never throws) when the path doesn't exist or can't be stat'd. */
|
|
23
|
+
getMtimeMs(path: string): number | undefined;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
interface ArmadaStatus {
|
|
24
|
-
vehicles?: Array<{ name?: string; executable?: string; nativeStatus?: string; ready?: boolean }>;
|
|
27
|
+
vehicles?: Array<{ name?: string; executable?: string; nativeStatus?: string; ready?: boolean; nativePid?: number }>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A boot/registration race can leave a process's own start time a few seconds behind its package.json's write; only a gap past this is treated as a genuinely stale running daemon. */
|
|
31
|
+
const STALE_CODE_GRACE_MS = 15_000;
|
|
32
|
+
|
|
33
|
+
/** Undefined (never throws) when `pid` isn't a running process this host can inspect, or `ps` isn't available. */
|
|
34
|
+
function processStartTimeMs(pid: number, deps: ServiceDoctorDeps): number | undefined {
|
|
35
|
+
const result = deps.runCommand("ps", ["-o", "lstart=", "-p", String(pid)]);
|
|
36
|
+
if (!result.ok) return undefined;
|
|
37
|
+
const parsed = Date.parse(result.output.trim());
|
|
38
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
export function checkServiceUnitPaths(
|
|
@@ -96,6 +110,20 @@ export function checkServiceUnitPaths(
|
|
|
96
110
|
unitName: managedPackage.spec.name,
|
|
97
111
|
message: `${managedPackage.spec.name}'s Armada service is ${vehicle.nativeStatus}; start it through Armada before connecting`,
|
|
98
112
|
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (vehicle.nativePid !== undefined) {
|
|
116
|
+
const packageJsonMtime = deps.getMtimeMs(join(piHome, "npm", "node_modules", managedPackage.packageName, "package.json"));
|
|
117
|
+
const startedAt = processStartTimeMs(vehicle.nativePid, deps);
|
|
118
|
+
if (packageJsonMtime !== undefined && startedAt !== undefined && startedAt < packageJsonMtime - STALE_CODE_GRACE_MS) {
|
|
119
|
+
diagnostics.push({
|
|
120
|
+
code: "SERVICE_STALE_CODE",
|
|
121
|
+
severity: "warning",
|
|
122
|
+
package: managedPackage.packageName,
|
|
123
|
+
unitName: managedPackage.spec.name,
|
|
124
|
+
message: `${managedPackage.spec.name}'s process (pid ${vehicle.nativePid}) started before its own package.json was last written -- it is very likely still running old in-memory code; restart it through Armada`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
99
127
|
}
|
|
100
128
|
}
|
|
101
129
|
return { ok: diagnostics.length === 0, diagnostics, checked };
|
|
@@ -37,6 +37,7 @@ function fakeDeps(status: unknown, fileExists = true): ServiceDoctorDeps {
|
|
|
37
37
|
return {
|
|
38
38
|
armadaCliPath: "/armada/cli.js",
|
|
39
39
|
fileExists: () => fileExists,
|
|
40
|
+
getMtimeMs: () => undefined,
|
|
40
41
|
runCommand: () => ({ ok: true, output: JSON.stringify(status) }),
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -125,6 +126,59 @@ describe("checkServiceUnitPaths", () => {
|
|
|
125
126
|
});
|
|
126
127
|
});
|
|
127
128
|
|
|
129
|
+
it("reports a running Vehicle whose process predates its own package.json as stale code, not an error", () => {
|
|
130
|
+
const home = piHome(["npm:fakedaemon"]);
|
|
131
|
+
const dir = installDaemonPackage(home, "fakedaemon");
|
|
132
|
+
const deps: ServiceDoctorDeps = {
|
|
133
|
+
armadaCliPath: "/armada/cli.js",
|
|
134
|
+
fileExists: () => true,
|
|
135
|
+
getMtimeMs: () => Date.now(),
|
|
136
|
+
runCommand: (command) =>
|
|
137
|
+
command === "ps"
|
|
138
|
+
? { ok: true, output: new Date(Date.now() - 60_000).toString() }
|
|
139
|
+
: {
|
|
140
|
+
ok: true,
|
|
141
|
+
output: JSON.stringify({
|
|
142
|
+
vehicles: [{ name: "fakedaemon", executable: join(dir, "cli.ts"), nativeStatus: "running", nativePid: 4242 }],
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const report = checkServiceUnitPaths(home, undefined, deps);
|
|
147
|
+
expect(report).toEqual({
|
|
148
|
+
ok: false,
|
|
149
|
+
checked: 1,
|
|
150
|
+
diagnostics: [
|
|
151
|
+
{
|
|
152
|
+
code: "SERVICE_STALE_CODE",
|
|
153
|
+
severity: "warning",
|
|
154
|
+
package: "fakedaemon",
|
|
155
|
+
unitName: "fakedaemon",
|
|
156
|
+
message: expect.stringContaining("pid 4242"),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("never flags a running Vehicle whose process started after its own package.json was last written", () => {
|
|
163
|
+
const home = piHome(["npm:fakedaemon"]);
|
|
164
|
+
const dir = installDaemonPackage(home, "fakedaemon");
|
|
165
|
+
const deps: ServiceDoctorDeps = {
|
|
166
|
+
armadaCliPath: "/armada/cli.js",
|
|
167
|
+
fileExists: () => true,
|
|
168
|
+
getMtimeMs: () => Date.now() - 60_000,
|
|
169
|
+
runCommand: (command) =>
|
|
170
|
+
command === "ps"
|
|
171
|
+
? { ok: true, output: new Date().toString() }
|
|
172
|
+
: {
|
|
173
|
+
ok: true,
|
|
174
|
+
output: JSON.stringify({
|
|
175
|
+
vehicles: [{ name: "fakedaemon", executable: join(dir, "cli.ts"), nativeStatus: "running", nativePid: 4242 }],
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
expect(checkServiceUnitPaths(home, undefined, deps)).toEqual({ ok: true, diagnostics: [], checked: 1 });
|
|
180
|
+
});
|
|
181
|
+
|
|
128
182
|
it("reports Armada status failure without guessing from native descriptors", () => {
|
|
129
183
|
const home = piHome(["npm:fakedaemon"]);
|
|
130
184
|
installDaemonPackage(home, "fakedaemon");
|