codex-plugin-doctor 1.46.0 → 1.46.1
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/README.md +12 -6
- package/dist/core/release-check.d.ts +17 -2
- package/dist/core/release-check.js +89 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/run-cli.js +30 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -285,9 +285,13 @@ codex-plugin-doctor check . --badge-markdown
|
|
|
285
285
|
codex-plugin-doctor check . --sarif --output results.sarif
|
|
286
286
|
codex-plugin-doctor check . --ascii
|
|
287
287
|
codex-plugin-doctor check . --no-animations
|
|
288
|
-
codex-plugin-doctor check . --runtime
|
|
289
|
-
codex-plugin-doctor check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
|
|
290
|
-
codex-plugin-doctor check .
|
|
288
|
+
codex-plugin-doctor check . --runtime
|
|
289
|
+
codex-plugin-doctor check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
|
|
290
|
+
codex-plugin-doctor release check .
|
|
291
|
+
codex-plugin-doctor release check . --json
|
|
292
|
+
codex-plugin-doctor release check . --runtime
|
|
293
|
+
codex-plugin-doctor release check . --runtime --require-runtime-approval --runtime-approval-digest sha256:<approved-plan-digest>
|
|
294
|
+
codex-plugin-doctor check . --config .codex-doctor.json
|
|
291
295
|
codex-plugin-doctor baseline create . --output .codex-doctor-baseline.json
|
|
292
296
|
codex-plugin-doctor check . --baseline .codex-doctor-baseline.json
|
|
293
297
|
codex-plugin-doctor check . --history validation-history.jsonl
|
|
@@ -406,9 +410,9 @@ jobs:
|
|
|
406
410
|
runs-on: ubuntu-latest
|
|
407
411
|
steps:
|
|
408
412
|
- uses: actions/checkout@v5
|
|
409
|
-
- uses: Esquetta/CodexPluginDoctor@v1.46.
|
|
413
|
+
- uses: Esquetta/CodexPluginDoctor@v1.46.1
|
|
410
414
|
with:
|
|
411
|
-
version: "1.46.
|
|
415
|
+
version: "1.46.1"
|
|
412
416
|
path: .
|
|
413
417
|
runtime: "true"
|
|
414
418
|
policy: codex-publish
|
|
@@ -463,7 +467,9 @@ npm run prepare-release
|
|
|
463
467
|
npm run release-check
|
|
464
468
|
```
|
|
465
469
|
|
|
466
|
-
`prepare-release` runs tests, builds the TypeScript output, and performs `npm pack --dry-run`. `release-check` adds release preflight checks for a clean git tree, existing npm versions, existing version tags, tests, build, pack dry-run, fresh temp install audit, and publish dry-run.
|
|
470
|
+
`prepare-release` runs tests, builds the TypeScript output, and performs `npm pack --dry-run`. `release-check` adds release preflight checks for a clean git tree, existing npm versions, existing version tags, package/lockfile/changelog version consistency, tests, build, pack dry-run, fresh temp install audit, and publish dry-run.
|
|
471
|
+
|
|
472
|
+
For plugin packages, `codex-plugin-doctor release check .` performs static readiness checks by default. Runtime probing executes package-local MCP servers and therefore requires an explicit `--runtime`; protected workflows can also require the approved runtime-plan digest.
|
|
467
473
|
|
|
468
474
|
Related docs:
|
|
469
475
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { CheckOptions, CheckResult } from "../domain/types.js";
|
|
1
2
|
export interface ReleaseCheckReport {
|
|
2
3
|
targetPath: string;
|
|
3
4
|
status: "pass" | "fail";
|
|
5
|
+
runtimeProbeEnabled?: boolean;
|
|
4
6
|
checks: {
|
|
5
7
|
validation: {
|
|
6
8
|
status: string;
|
|
@@ -24,12 +26,25 @@ export interface ReleaseCheckReport {
|
|
|
24
26
|
status: string;
|
|
25
27
|
score: number;
|
|
26
28
|
};
|
|
29
|
+
metadata?: ReleaseMetadataCheck;
|
|
27
30
|
};
|
|
28
31
|
ready: boolean;
|
|
29
32
|
}
|
|
30
|
-
export
|
|
33
|
+
export interface ReleaseMetadataCheck {
|
|
34
|
+
status: "pass" | "fail" | "skipped";
|
|
35
|
+
packageVersion: string | null;
|
|
36
|
+
lockfileVersion: string | null;
|
|
37
|
+
changelogVersion: boolean | null;
|
|
38
|
+
findings: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface BuildReleaseCheckOptions {
|
|
31
41
|
env?: Record<string, string | undefined>;
|
|
32
42
|
platform?: NodeJS.Platform;
|
|
33
|
-
|
|
43
|
+
runtime?: boolean;
|
|
44
|
+
runCheck?: (targetPath: string, options: CheckOptions) => Promise<CheckResult>;
|
|
45
|
+
}
|
|
46
|
+
export declare function buildReleaseMetadataCheck(targetPath: string): Promise<ReleaseMetadataCheck>;
|
|
47
|
+
export declare function assertReleaseMetadataSync(targetPath: string): Promise<void>;
|
|
48
|
+
export declare function buildReleaseCheck(targetPath: string, options?: BuildReleaseCheckOptions): Promise<ReleaseCheckReport>;
|
|
34
49
|
export declare function renderReleaseCheck(report: ReleaseCheckReport): string;
|
|
35
50
|
export declare function renderReleaseCheckJson(report: ReleaseCheckReport): string;
|
|
@@ -1,9 +1,83 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { validatePlugin } from "./validate-plugin.js";
|
|
3
4
|
import { buildSecurityAudit } from "../security/security-audit.js";
|
|
4
5
|
import { buildDepAudit } from "./dep-audit.js";
|
|
5
6
|
import { buildCompatibilityMatrix } from "../compatibility/compatibility-matrix.js";
|
|
6
7
|
import { buildTrustScore } from "../security/trust-score.js";
|
|
8
|
+
async function readOptionalFile(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
return await readFile(filePath, "utf8");
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code === "ENOENT") {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function buildReleaseMetadataCheck(targetPath) {
|
|
20
|
+
const packageContent = await readOptionalFile(path.join(targetPath, "package.json"));
|
|
21
|
+
if (packageContent === null) {
|
|
22
|
+
return {
|
|
23
|
+
status: "skipped",
|
|
24
|
+
packageVersion: null,
|
|
25
|
+
lockfileVersion: null,
|
|
26
|
+
changelogVersion: null,
|
|
27
|
+
findings: []
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const findings = [];
|
|
31
|
+
let packageVersion = null;
|
|
32
|
+
let lockfileVersion = null;
|
|
33
|
+
try {
|
|
34
|
+
const packageJson = JSON.parse(packageContent);
|
|
35
|
+
packageVersion = typeof packageJson.version === "string" ? packageJson.version : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
findings.push("package.json is not valid JSON.");
|
|
39
|
+
}
|
|
40
|
+
if (!packageVersion) {
|
|
41
|
+
findings.push("package.json must contain a version.");
|
|
42
|
+
}
|
|
43
|
+
const lockContent = await readOptionalFile(path.join(targetPath, "package-lock.json"));
|
|
44
|
+
if (lockContent !== null) {
|
|
45
|
+
try {
|
|
46
|
+
const lock = JSON.parse(lockContent);
|
|
47
|
+
const rootVersion = lock.packages?.[""]?.version ?? lock.version;
|
|
48
|
+
lockfileVersion = typeof rootVersion === "string" ? rootVersion : null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
findings.push("package-lock.json is not valid JSON.");
|
|
52
|
+
}
|
|
53
|
+
if (!lockfileVersion) {
|
|
54
|
+
findings.push("package-lock.json must contain the root package version.");
|
|
55
|
+
}
|
|
56
|
+
else if (packageVersion && lockfileVersion !== packageVersion) {
|
|
57
|
+
findings.push(`package-lock.json version ${lockfileVersion} does not match package.json ${packageVersion}.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const changelog = await readOptionalFile(path.join(targetPath, "CHANGELOG.md"));
|
|
61
|
+
const changelogVersion = changelog === null || !packageVersion
|
|
62
|
+
? null
|
|
63
|
+
: new RegExp(`^## \\[${packageVersion.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\](?:\\s|$)`, "m").test(changelog);
|
|
64
|
+
if (changelogVersion === false) {
|
|
65
|
+
findings.push(`CHANGELOG.md does not contain a ${packageVersion} release section.`);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
status: findings.length === 0 ? "pass" : "fail",
|
|
69
|
+
packageVersion,
|
|
70
|
+
lockfileVersion,
|
|
71
|
+
changelogVersion,
|
|
72
|
+
findings
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export async function assertReleaseMetadataSync(targetPath) {
|
|
76
|
+
const metadata = await buildReleaseMetadataCheck(path.resolve(targetPath));
|
|
77
|
+
if (metadata.status === "fail") {
|
|
78
|
+
throw new Error(`Release metadata is inconsistent: ${metadata.findings.join(" ")}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
7
81
|
function matrixOverallStatus(matrix) {
|
|
8
82
|
const statuses = matrix.results.map((r) => r.status);
|
|
9
83
|
const passCount = statuses.filter((s) => s === "pass").length;
|
|
@@ -29,11 +103,15 @@ export async function buildReleaseCheck(targetPath, options = {}) {
|
|
|
29
103
|
const resolvedPath = path.resolve(targetPath);
|
|
30
104
|
const env = options.env ?? process.env;
|
|
31
105
|
const platform = options.platform ?? process.platform;
|
|
32
|
-
const
|
|
106
|
+
const runtimeProbeEnabled = options.runtime ?? false;
|
|
107
|
+
const validationResult = await (options.runCheck ?? validatePlugin)(resolvedPath, {
|
|
108
|
+
runtime: runtimeProbeEnabled
|
|
109
|
+
});
|
|
33
110
|
const securityResult = await buildSecurityAudit(resolvedPath);
|
|
34
111
|
const depResult = await buildDepAudit(resolvedPath);
|
|
35
112
|
const compatResult = await buildCompatibilityMatrix(resolvedPath, { env, platform });
|
|
36
113
|
const trustResult = await buildTrustScore(resolvedPath);
|
|
114
|
+
const metadataResult = await buildReleaseMetadataCheck(resolvedPath);
|
|
37
115
|
const checks = {
|
|
38
116
|
validation: {
|
|
39
117
|
status: validationResult.status,
|
|
@@ -56,16 +134,19 @@ export async function buildReleaseCheck(targetPath, options = {}) {
|
|
|
56
134
|
trust: {
|
|
57
135
|
status: trustResult.status,
|
|
58
136
|
score: trustResult.score
|
|
59
|
-
}
|
|
137
|
+
},
|
|
138
|
+
metadata: metadataResult
|
|
60
139
|
};
|
|
61
140
|
const ready = checks.validation.status === "pass" &&
|
|
62
141
|
checks.security.status === "pass" &&
|
|
63
142
|
checks.dependencies.status !== "fail" &&
|
|
64
143
|
checks.compatibility.status !== "fail" &&
|
|
65
|
-
checks.trust.status === "pass"
|
|
144
|
+
checks.trust.status === "pass" &&
|
|
145
|
+
checks.metadata.status !== "fail";
|
|
66
146
|
return {
|
|
67
147
|
targetPath: resolvedPath,
|
|
68
148
|
status: ready ? "pass" : "fail",
|
|
149
|
+
runtimeProbeEnabled,
|
|
69
150
|
checks,
|
|
70
151
|
ready
|
|
71
152
|
};
|
|
@@ -77,6 +158,7 @@ export function renderReleaseCheck(report) {
|
|
|
77
158
|
"==================================",
|
|
78
159
|
`Path: ${report.targetPath}`,
|
|
79
160
|
`Ready: ${report.ready ? "YES" : "NO"}`,
|
|
161
|
+
`Runtime probe: ${report.runtimeProbeEnabled ? "enabled" : "disabled"}`,
|
|
80
162
|
"",
|
|
81
163
|
"Checks",
|
|
82
164
|
"------",
|
|
@@ -85,6 +167,9 @@ export function renderReleaseCheck(report) {
|
|
|
85
167
|
` ${icon(report.checks.dependencies.status === "pass")} dependencies ${report.checks.dependencies.status} (${report.checks.dependencies.vulnerabilities} vulns)`,
|
|
86
168
|
` ${icon(report.checks.compatibility.status === "pass")} compatibility ${report.checks.compatibility.status} (score: ${report.checks.compatibility.score})`,
|
|
87
169
|
` ${icon(report.checks.trust.status === "pass")} trust ${report.checks.trust.status} (score: ${report.checks.trust.score})`,
|
|
170
|
+
...(report.checks.metadata
|
|
171
|
+
? [` ${icon(report.checks.metadata.status !== "fail")} metadata ${report.checks.metadata.status} (${report.checks.metadata.findings.length} findings)`]
|
|
172
|
+
: []),
|
|
88
173
|
"",
|
|
89
174
|
report.ready
|
|
90
175
|
? "All release checks passed. Ready to publish."
|
|
@@ -97,6 +182,7 @@ export function renderReleaseCheckJson(report) {
|
|
|
97
182
|
schemaVersion: "1.0.0",
|
|
98
183
|
targetPath: report.targetPath,
|
|
99
184
|
status: report.status,
|
|
185
|
+
runtimeProbeEnabled: report.runtimeProbeEnabled ?? false,
|
|
100
186
|
ready: report.ready,
|
|
101
187
|
checks: report.checks
|
|
102
188
|
}, null, 2);
|
package/dist/index.d.ts
CHANGED
|
@@ -29,5 +29,5 @@ export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepA
|
|
|
29
29
|
export { initGitHooks, removeGitHooks, type InitGitHooksResult, type RemoveGitHooksResult } from "./core/init-git-hooks.js";
|
|
30
30
|
export { generateCompletion, type CompletionShell } from "./core/shell-completion.js";
|
|
31
31
|
export { validateConfigFile, renderConfigValidation, renderConfigValidationJson, type ConfigValidationFinding, type ConfigValidationReport } from "./core/config-validate.js";
|
|
32
|
-
export { buildReleaseCheck, renderReleaseCheck, renderReleaseCheckJson, type ReleaseCheckReport } from "./core/release-check.js";
|
|
32
|
+
export { assertReleaseMetadataSync, buildReleaseCheck, buildReleaseMetadataCheck, renderReleaseCheck, renderReleaseCheckJson, type BuildReleaseCheckOptions, type ReleaseMetadataCheck, type ReleaseCheckReport } from "./core/release-check.js";
|
|
33
33
|
export declare function runCheck(targetPath: string, options?: CheckOptions): Promise<CheckResult>;
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepA
|
|
|
29
29
|
export { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
|
|
30
30
|
export { generateCompletion } from "./core/shell-completion.js";
|
|
31
31
|
export { validateConfigFile, renderConfigValidation, renderConfigValidationJson } from "./core/config-validate.js";
|
|
32
|
-
export { buildReleaseCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
|
|
32
|
+
export { assertReleaseMetadataSync, buildReleaseCheck, buildReleaseMetadataCheck, renderReleaseCheck, renderReleaseCheckJson } from "./core/release-check.js";
|
|
33
33
|
export async function runCheck(targetPath, options = {}) {
|
|
34
34
|
return validatePlugin(targetPath, options);
|
|
35
35
|
}
|
package/dist/run-cli.js
CHANGED
|
@@ -2253,7 +2253,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2253
2253
|
if (command === "release") {
|
|
2254
2254
|
const subCommand = maybePath;
|
|
2255
2255
|
if (subCommand !== "check") {
|
|
2256
|
-
io.writeStderr("Usage: codex-plugin-doctor release check <path> [--json]");
|
|
2256
|
+
io.writeStderr("Usage: codex-plugin-doctor release check <path> [--json] [--runtime] [--require-runtime-approval --runtime-approval-digest <digest>]");
|
|
2257
2257
|
return 2;
|
|
2258
2258
|
}
|
|
2259
2259
|
const targetPath = remainingArgs[0] && !remainingArgs[0].startsWith("--") ? remainingArgs[0] : ".";
|
|
@@ -2261,9 +2261,37 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2261
2261
|
? remainingArgs
|
|
2262
2262
|
: remainingArgs.slice(1);
|
|
2263
2263
|
const jsonOutput = releaseFlags.includes("--json");
|
|
2264
|
+
const runtimeProbeEnabled = releaseFlags.includes("--runtime");
|
|
2265
|
+
const requireRuntimeApproval = releaseFlags.includes("--require-runtime-approval");
|
|
2266
|
+
const runtimeApprovalDigestIndex = releaseFlags.indexOf("--runtime-approval-digest");
|
|
2267
|
+
const runtimeApprovalDigest = runtimeApprovalDigestIndex === -1
|
|
2268
|
+
? null
|
|
2269
|
+
: releaseFlags[runtimeApprovalDigestIndex + 1];
|
|
2270
|
+
if (requireRuntimeApproval && !runtimeProbeEnabled) {
|
|
2271
|
+
io.writeStderr("Runtime approval requires --runtime.");
|
|
2272
|
+
return 2;
|
|
2273
|
+
}
|
|
2274
|
+
if (runtimeApprovalDigestIndex !== -1 &&
|
|
2275
|
+
(!runtimeApprovalDigest || runtimeApprovalDigest.startsWith("--"))) {
|
|
2276
|
+
io.writeStderr("Missing digest after --runtime-approval-digest.");
|
|
2277
|
+
return 2;
|
|
2278
|
+
}
|
|
2279
|
+
if (requireRuntimeApproval) {
|
|
2280
|
+
const runtimePlan = await buildDoctorRuntimePlan(targetPath);
|
|
2281
|
+
const approval = evaluateRuntimeApproval(runtimePlan, {
|
|
2282
|
+
required: true,
|
|
2283
|
+
approvedDigest: runtimeApprovalDigest
|
|
2284
|
+
});
|
|
2285
|
+
if (!runtimeApprovalPassed(approval)) {
|
|
2286
|
+
io.writeStderr(`${approval.message}\nCurrent runtime plan digest: ${runtimePlan.digest}`);
|
|
2287
|
+
return 1;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2264
2290
|
const report = await buildReleaseCheck(targetPath, {
|
|
2265
2291
|
env: terminalContext.env,
|
|
2266
|
-
platform: terminalContext.platform
|
|
2292
|
+
platform: terminalContext.platform,
|
|
2293
|
+
runtime: runtimeProbeEnabled,
|
|
2294
|
+
runCheck: options.runCheckImpl
|
|
2267
2295
|
});
|
|
2268
2296
|
const output = jsonOutput
|
|
2269
2297
|
? renderReleaseCheckJson(report)
|
package/package.json
CHANGED