@peac/cli 0.12.9 → 0.12.11
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/dist/commands/doctor.d.ts +22 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/index.cjs +208 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +208 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `peac doctor`: installability diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Runs a fixed set of offline-first checks that flag common PEAC
|
|
5
|
+
* installation problems (Node version, required package resolution,
|
|
6
|
+
* tracked config presence). Remote checks (JWKS reachability, reference
|
|
7
|
+
* verifier reachability) are opt-in via the --online flag; by default
|
|
8
|
+
* the command performs zero network calls.
|
|
9
|
+
*
|
|
10
|
+
* Each check reports green / yellow / red. Exit code is 0 when all
|
|
11
|
+
* required checks are green or yellow; 1 when any required check is
|
|
12
|
+
* red.
|
|
13
|
+
*/
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
export declare function runDoctor(options: {
|
|
16
|
+
online: boolean;
|
|
17
|
+
issuer?: string;
|
|
18
|
+
timeout: number;
|
|
19
|
+
cwd: string;
|
|
20
|
+
}): Promise<number>;
|
|
21
|
+
export declare function doctor(program: Command): void;
|
|
22
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkLpC,wBAAsB,SAAS,CAAC,OAAO,EAAE;IACvC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,MAAM,CAAC,CAkClB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqB7C"}
|
package/dist/index.cjs
CHANGED
|
@@ -12,6 +12,7 @@ var crypto$1 = require('crypto');
|
|
|
12
12
|
var schema = require('@peac/schema');
|
|
13
13
|
var disc = require('@peac/disc');
|
|
14
14
|
var audit = require('@peac/audit');
|
|
15
|
+
var module$1 = require('module');
|
|
15
16
|
|
|
16
17
|
function _interopNamespace(e) {
|
|
17
18
|
if (e && e.__esModule) return e;
|
|
@@ -586,9 +587,9 @@ policy.command("show-profile").description("Show details of a specific policy pr
|
|
|
586
587
|
function sha256(content) {
|
|
587
588
|
return crypto$1.createHash("sha256").update(content, "utf8").digest("hex");
|
|
588
589
|
}
|
|
589
|
-
function zodPathToJsonPointer(
|
|
590
|
-
if (
|
|
591
|
-
return "/" +
|
|
590
|
+
function zodPathToJsonPointer(path9) {
|
|
591
|
+
if (path9.length === 0) return "";
|
|
592
|
+
return "/" + path9.map((segment) => {
|
|
592
593
|
const str = String(segment);
|
|
593
594
|
return str.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
594
595
|
}).join("/");
|
|
@@ -2389,6 +2390,209 @@ function reconcileCommand() {
|
|
|
2389
2390
|
);
|
|
2390
2391
|
return reconcile;
|
|
2391
2392
|
}
|
|
2393
|
+
var requireFrom = module$1.createRequire(path6__namespace.join(process.cwd(), "package.json"));
|
|
2394
|
+
function paint(status, msg) {
|
|
2395
|
+
const labels = {
|
|
2396
|
+
green: "PASS",
|
|
2397
|
+
yellow: "WARN",
|
|
2398
|
+
red: "FAIL"
|
|
2399
|
+
};
|
|
2400
|
+
return `[${labels[status]}] ${msg}`;
|
|
2401
|
+
}
|
|
2402
|
+
function parseMajor(nodeVersion) {
|
|
2403
|
+
const m = /^v(\d+)\./.exec(nodeVersion);
|
|
2404
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
2405
|
+
}
|
|
2406
|
+
function checkNodeVersion() {
|
|
2407
|
+
const major = parseMajor(process.version);
|
|
2408
|
+
if (major >= 22) {
|
|
2409
|
+
return {
|
|
2410
|
+
name: "Node version",
|
|
2411
|
+
status: "green",
|
|
2412
|
+
detail: `${process.version} (>= 22 required)`
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
return {
|
|
2416
|
+
name: "Node version",
|
|
2417
|
+
status: "red",
|
|
2418
|
+
detail: `${process.version} is below the required Node >= 22`
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
function checkPackageResolution() {
|
|
2422
|
+
const required = ["@peac/protocol", "@peac/crypto", "@peac/schema", "@peac/kernel"];
|
|
2423
|
+
const missing = [];
|
|
2424
|
+
for (const pkg of required) {
|
|
2425
|
+
try {
|
|
2426
|
+
requireFrom.resolve(pkg);
|
|
2427
|
+
} catch {
|
|
2428
|
+
missing.push(pkg);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
if (missing.length === 0) {
|
|
2432
|
+
return {
|
|
2433
|
+
name: "Package resolution",
|
|
2434
|
+
status: "green",
|
|
2435
|
+
detail: `${required.length} required packages resolvable`
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
return {
|
|
2439
|
+
name: "Package resolution",
|
|
2440
|
+
status: "red",
|
|
2441
|
+
detail: `${missing.length} missing: ${missing.join(", ")}`
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
function checkKeyMaterial(cwd) {
|
|
2445
|
+
const env = process.env.PEAC_ISSUER_KEY || "";
|
|
2446
|
+
if (env) {
|
|
2447
|
+
if (env.startsWith("env:") || env.startsWith("file:")) {
|
|
2448
|
+
return {
|
|
2449
|
+
name: "Issuer key material",
|
|
2450
|
+
status: "green",
|
|
2451
|
+
detail: `PEAC_ISSUER_KEY set via ${env.split(":")[0]}:... scheme`
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
return {
|
|
2455
|
+
name: "Issuer key material",
|
|
2456
|
+
status: "yellow",
|
|
2457
|
+
detail: "PEAC_ISSUER_KEY set but not using env: or file: scheme"
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
const candidatePaths = [
|
|
2461
|
+
path6__namespace.join(cwd, "issuer.jwk.json"),
|
|
2462
|
+
path6__namespace.join(cwd, ".peac/issuer.jwk.json")
|
|
2463
|
+
];
|
|
2464
|
+
for (const p of candidatePaths) {
|
|
2465
|
+
if (fs6__namespace.existsSync(p)) {
|
|
2466
|
+
return {
|
|
2467
|
+
name: "Issuer key material",
|
|
2468
|
+
status: "yellow",
|
|
2469
|
+
detail: `found ${path6__namespace.relative(cwd, p)}; prefer PEAC_ISSUER_KEY env var`
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
return {
|
|
2474
|
+
name: "Issuer key material",
|
|
2475
|
+
status: "yellow",
|
|
2476
|
+
detail: "not found (issuance disabled; read-only tools still work)"
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
function checkPeacTxt(cwd) {
|
|
2480
|
+
const p = path6__namespace.join(cwd, ".well-known/peac.txt");
|
|
2481
|
+
if (fs6__namespace.existsSync(p)) {
|
|
2482
|
+
return {
|
|
2483
|
+
name: "peac.txt presence",
|
|
2484
|
+
status: "green",
|
|
2485
|
+
detail: `.well-known/peac.txt present at ${p}`
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
return {
|
|
2489
|
+
name: "peac.txt presence",
|
|
2490
|
+
status: "yellow",
|
|
2491
|
+
detail: "no .well-known/peac.txt (discovery-layer check; optional for tool callers)"
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
async function checkOnlineJwks(issuerUrl, timeoutMs) {
|
|
2495
|
+
const ctrl = new AbortController();
|
|
2496
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
2497
|
+
try {
|
|
2498
|
+
const cfgUrl = new URL("/.well-known/peac-issuer.json", issuerUrl).toString();
|
|
2499
|
+
const res = await fetch(cfgUrl, { signal: ctrl.signal });
|
|
2500
|
+
if (res.status !== 200) {
|
|
2501
|
+
return {
|
|
2502
|
+
name: "Issuer configuration",
|
|
2503
|
+
status: "red",
|
|
2504
|
+
detail: `${cfgUrl} returned ${res.status}`
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
const cfg = await res.json();
|
|
2508
|
+
const jwksUri = cfg.jwks_uri;
|
|
2509
|
+
if (!jwksUri) {
|
|
2510
|
+
return {
|
|
2511
|
+
name: "Issuer configuration",
|
|
2512
|
+
status: "red",
|
|
2513
|
+
detail: `${cfgUrl} missing jwks_uri`
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
const jwksRes = await fetch(jwksUri, { signal: ctrl.signal });
|
|
2517
|
+
if (jwksRes.status !== 200) {
|
|
2518
|
+
return {
|
|
2519
|
+
name: "Issuer configuration",
|
|
2520
|
+
status: "red",
|
|
2521
|
+
detail: `jwks_uri ${jwksUri} returned ${jwksRes.status}`
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
const jwks = await jwksRes.json();
|
|
2525
|
+
const keyCount = Array.isArray(jwks.keys) ? jwks.keys.length : 0;
|
|
2526
|
+
if (keyCount === 0) {
|
|
2527
|
+
return {
|
|
2528
|
+
name: "Issuer configuration",
|
|
2529
|
+
status: "red",
|
|
2530
|
+
detail: `jwks_uri ${jwksUri} returned empty keys array`
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
return {
|
|
2534
|
+
name: "Issuer configuration",
|
|
2535
|
+
status: "green",
|
|
2536
|
+
detail: `${cfgUrl} + jwks_uri resolvable with ${keyCount} key(s)`
|
|
2537
|
+
};
|
|
2538
|
+
} catch (err) {
|
|
2539
|
+
return {
|
|
2540
|
+
name: "Issuer configuration",
|
|
2541
|
+
status: "red",
|
|
2542
|
+
detail: `fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2543
|
+
};
|
|
2544
|
+
} finally {
|
|
2545
|
+
clearTimeout(timer);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
async function runDoctor(options) {
|
|
2549
|
+
const results = [];
|
|
2550
|
+
results.push(checkNodeVersion());
|
|
2551
|
+
results.push(checkPackageResolution());
|
|
2552
|
+
results.push(checkKeyMaterial(options.cwd));
|
|
2553
|
+
results.push(checkPeacTxt(options.cwd));
|
|
2554
|
+
if (options.online) {
|
|
2555
|
+
if (!options.issuer) {
|
|
2556
|
+
results.push({
|
|
2557
|
+
name: "Issuer configuration",
|
|
2558
|
+
status: "yellow",
|
|
2559
|
+
detail: "--online set but --issuer <url> not provided; skipping remote check"
|
|
2560
|
+
});
|
|
2561
|
+
} else {
|
|
2562
|
+
results.push(await checkOnlineJwks(options.issuer, options.timeout));
|
|
2563
|
+
}
|
|
2564
|
+
} else {
|
|
2565
|
+
results.push({
|
|
2566
|
+
name: "Issuer configuration",
|
|
2567
|
+
status: "yellow",
|
|
2568
|
+
detail: "offline mode (default); pass --online --issuer <url> to enable remote check"
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
console.log("\npeac doctor");
|
|
2572
|
+
console.log("-----------");
|
|
2573
|
+
for (const r of results) {
|
|
2574
|
+
console.log(paint(r.status, `${r.name}: ${r.detail}`));
|
|
2575
|
+
}
|
|
2576
|
+
const hasRed = results.some((r) => r.status === "red");
|
|
2577
|
+
console.log(hasRed ? "\nFAIL: one or more checks are red." : "\nOK: no red checks.");
|
|
2578
|
+
return hasRed ? 1 : 0;
|
|
2579
|
+
}
|
|
2580
|
+
function doctor(program2) {
|
|
2581
|
+
program2.command("doctor").description("Run PEAC installability diagnostics (offline by default)").option("--online", "enable remote checks (issuer configuration, JWKS)", false).option("--issuer <url>", "issuer URL for --online checks (https://...)").option(
|
|
2582
|
+
"--timeout <ms>",
|
|
2583
|
+
"timeout for remote checks in milliseconds",
|
|
2584
|
+
(v) => parseInt(v, 10),
|
|
2585
|
+
5e3
|
|
2586
|
+
).action(async (opts) => {
|
|
2587
|
+
const exitCode = await runDoctor({
|
|
2588
|
+
online: !!opts.online,
|
|
2589
|
+
issuer: opts.issuer,
|
|
2590
|
+
timeout: opts.timeout,
|
|
2591
|
+
cwd: process.cwd()
|
|
2592
|
+
});
|
|
2593
|
+
process.exit(exitCode);
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2392
2596
|
|
|
2393
2597
|
// src/index.ts
|
|
2394
2598
|
var program = new commander.Command();
|
|
@@ -2525,6 +2729,7 @@ program.addCommand(policy);
|
|
|
2525
2729
|
program.addCommand(conformance);
|
|
2526
2730
|
program.addCommand(samples);
|
|
2527
2731
|
program.addCommand(reconcileCommand());
|
|
2732
|
+
doctor(program);
|
|
2528
2733
|
try {
|
|
2529
2734
|
program.parse();
|
|
2530
2735
|
} catch (err) {
|