codex-plugin-doctor 1.54.0 → 1.55.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/README.md +28 -2
- package/dist/core/mcp-registry-preflight.d.ts +38 -0
- package/dist/core/mcp-registry-preflight.js +358 -0
- package/dist/core/output-contract.js +92 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/run-cli.js +34 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,32 @@ codex-plugin-doctor registry inspect io.github.example/weather --allow-network
|
|
|
108
108
|
|
|
109
109
|
Local checks validate metadata, ownership consistency, package integrity, transports, and Codex installability. Exact Registry lookup is read-only, requires explicit network consent, and contacts only the fixed official Registry endpoint. It never follows advertised package, icon, repository, or remote MCP URLs. See [MCP Registry Readiness](./docs/architecture/mcp-registry-readiness.md).
|
|
110
110
|
|
|
111
|
+
### MCP Registry Publication Preflight
|
|
112
|
+
|
|
113
|
+
Check a publication candidate locally without making a network request:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
codex-plugin-doctor registry preflight path/to/server.json --json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Opt in to bounded public npm and MCP Registry metadata checks:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
codex-plugin-doctor registry preflight path/to/server.json --allow-network --json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Require a publish-ready result in CI:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
codex-plugin-doctor registry preflight path/to/server.json --allow-network --require-publish-ready --json
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Preflight checks the exact npm package version, then the exact Registry version
|
|
132
|
+
and latest Registry record on fixed public hosts. An existing exact Registry
|
|
133
|
+
version is an immutable collision and must be replaced by a version bump. The
|
|
134
|
+
command is advisory: it never authenticates, publishes, or changes npm or
|
|
135
|
+
Registry records. See [MCP Registry Publication Preflight](./docs/architecture/mcp-registry-publication-preflight.md).
|
|
136
|
+
|
|
111
137
|
Output formats:
|
|
112
138
|
|
|
113
139
|
- human text output
|
|
@@ -454,9 +480,9 @@ jobs:
|
|
|
454
480
|
runs-on: ubuntu-latest
|
|
455
481
|
steps:
|
|
456
482
|
- uses: actions/checkout@v5
|
|
457
|
-
- uses: Esquetta/CodexPluginDoctor@v1.
|
|
483
|
+
- uses: Esquetta/CodexPluginDoctor@v1.55.0
|
|
458
484
|
with:
|
|
459
|
-
version: "1.
|
|
485
|
+
version: "1.55.0"
|
|
460
486
|
path: .
|
|
461
487
|
runtime: "true"
|
|
462
488
|
policy: codex-publish
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type BoundedHttpRequestOptions, type BoundedHttpResponse } from "./bounded-http-client.js";
|
|
2
|
+
export type McpRegistryPublicationPreflightStatus = "pass" | "warn" | "fail";
|
|
3
|
+
type RegistryRequest = (url: string, options?: BoundedHttpRequestOptions) => Promise<BoundedHttpResponse>;
|
|
4
|
+
export interface McpRegistryPublicationPreflightFinding {
|
|
5
|
+
id: string;
|
|
6
|
+
severity: "warn" | "fail";
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
export interface McpRegistryPublicationPreflightReport {
|
|
10
|
+
schemaVersion: "1.0.0";
|
|
11
|
+
kind: "mcp-registry-publication-preflight";
|
|
12
|
+
generatedAt: string;
|
|
13
|
+
target: "server.json";
|
|
14
|
+
serverName?: string;
|
|
15
|
+
serverVersion?: string;
|
|
16
|
+
status: McpRegistryPublicationPreflightStatus;
|
|
17
|
+
localReadiness: McpRegistryPublicationPreflightStatus;
|
|
18
|
+
packagePublication: "pass" | "fail" | "skipped" | "unknown";
|
|
19
|
+
registryVersionAvailability: "available-first-publication" | "available-new-version" | "already-published" | "unknown";
|
|
20
|
+
publisherPlan: {
|
|
21
|
+
executable: false;
|
|
22
|
+
steps: Array<{
|
|
23
|
+
order: number;
|
|
24
|
+
command: "mcp-publisher login github" | "mcp-publisher publish";
|
|
25
|
+
purpose: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
findings: McpRegistryPublicationPreflightFinding[];
|
|
29
|
+
}
|
|
30
|
+
export interface BuildMcpRegistryPublicationPreflightOptions {
|
|
31
|
+
allowNetwork?: boolean;
|
|
32
|
+
request?: RegistryRequest;
|
|
33
|
+
}
|
|
34
|
+
export declare function buildMcpRegistryPublicationPreflight(targetPath: string, options?: BuildMcpRegistryPublicationPreflightOptions): Promise<McpRegistryPublicationPreflightReport>;
|
|
35
|
+
export declare function renderMcpRegistryPublicationPreflightJson(report: McpRegistryPublicationPreflightReport): string;
|
|
36
|
+
export declare function renderMcpRegistryPublicationPreflight(report: McpRegistryPublicationPreflightReport): string;
|
|
37
|
+
export declare function registryPublicationPreflightExitCode(report: McpRegistryPublicationPreflightReport, requirePublishReady?: boolean): 0 | 1;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildMcpRegistryReadiness } from "./mcp-registry.js";
|
|
4
|
+
import { requestBoundedHttp } from "./bounded-http-client.js";
|
|
5
|
+
const SERVER_NAME_PATTERN = /^[a-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+$/;
|
|
6
|
+
const VERSION_RANGE_PATTERN = /^(?:latest|[~^]|[<>]=?)|(?:\s+\|\|\s+)|(?:^|[.\s])[x*](?:$|[.\s])/i;
|
|
7
|
+
const OFFICIAL_SCHEMA_PATTERN = /^https:\/\/static\.modelcontextprotocol\.io\/schemas\/\d{4}-\d{2}-\d{2}\/server\.schema\.json$/;
|
|
8
|
+
const NPM_IDENTIFIER_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]{0,213}\/)?[a-z0-9][a-z0-9._-]{0,213}$/;
|
|
9
|
+
const INTEGRITY_PATTERN = /^(sha256|sha384|sha512)-([A-Za-z0-9+/]*={0,2})$/;
|
|
10
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
11
|
+
const NPM_REGISTRY = "https://registry.npmjs.org";
|
|
12
|
+
const MCP_REGISTRY = "https://registry.modelcontextprotocol.io";
|
|
13
|
+
const REQUEST_OPTIONS = {
|
|
14
|
+
method: "GET",
|
|
15
|
+
headers: {
|
|
16
|
+
accept: "application/json",
|
|
17
|
+
"user-agent": "codex-plugin-doctor"
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function isRecord(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
function publicFinding(finding) {
|
|
24
|
+
return {
|
|
25
|
+
id: finding.id,
|
|
26
|
+
severity: finding.severity,
|
|
27
|
+
message: finding.message
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function statusFromFindings(findings) {
|
|
31
|
+
if (findings.some((finding) => finding.severity === "fail")) {
|
|
32
|
+
return "fail";
|
|
33
|
+
}
|
|
34
|
+
return findings.some((finding) => finding.severity === "warn") ? "warn" : "pass";
|
|
35
|
+
}
|
|
36
|
+
function isSafeServerName(value) {
|
|
37
|
+
return value !== undefined && value.length >= 3 && value.length <= 200 && SERVER_NAME_PATTERN.test(value);
|
|
38
|
+
}
|
|
39
|
+
function isSafeServerVersion(value) {
|
|
40
|
+
return value !== undefined && value.length >= 1 && value.length <= 255
|
|
41
|
+
&& !/[\\/\u0000-\u001F\u007F]/.test(value) && !VERSION_RANGE_PATTERN.test(value);
|
|
42
|
+
}
|
|
43
|
+
function isSafeNpmIdentifier(value) {
|
|
44
|
+
return typeof value === "string" && NPM_IDENTIFIER_PATTERN.test(value);
|
|
45
|
+
}
|
|
46
|
+
function isIntegrity(value) {
|
|
47
|
+
if (typeof value !== "string") {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const match = INTEGRITY_PATTERN.exec(value);
|
|
51
|
+
if (!match || match[2].length % 4 !== 0) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const digest = Buffer.from(match[2], "base64");
|
|
55
|
+
const expectedBytes = match[1] === "sha256" ? 32 : match[1] === "sha384" ? 48 : 64;
|
|
56
|
+
return digest.length === expectedBytes && digest.toString("base64") === match[2];
|
|
57
|
+
}
|
|
58
|
+
function responseJson(response) {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(response.body.toString("utf8"));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function validNotFound(response) {
|
|
67
|
+
const payload = responseJson(response);
|
|
68
|
+
if (response.statusCode !== 404 || !isRecord(payload)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return (typeof payload.error === "string" && payload.error.trim().length > 0)
|
|
72
|
+
|| (payload.title === "Not Found"
|
|
73
|
+
&& payload.status === 404
|
|
74
|
+
&& typeof payload.detail === "string"
|
|
75
|
+
&& payload.detail.trim().length > 0);
|
|
76
|
+
}
|
|
77
|
+
function hasValidHttpUrl(value) {
|
|
78
|
+
if (typeof value !== "string" || value.length === 0 || /\s/.test(value)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL(value.replace(/\{[^{}]+\}/g, "template-value"));
|
|
83
|
+
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function hasValidPackageShape(value) {
|
|
90
|
+
if (!isRecord(value)
|
|
91
|
+
|| typeof value.registryType !== "string" || value.registryType.length === 0
|
|
92
|
+
|| typeof value.identifier !== "string" || value.identifier.length === 0
|
|
93
|
+
|| !isRecord(value.transport)
|
|
94
|
+
|| !["stdio", "streamable-http", "sse"].includes(value.transport.type)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (/^https?:\/\//i.test(value.identifier) && !hasValidHttpUrl(value.identifier)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (typeof value.version === "string" && VERSION_RANGE_PATTERN.test(value.version)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return value.registryType !== "mcpb" || (typeof value.fileSha256 === "string" && SHA256_PATTERN.test(value.fileSha256));
|
|
104
|
+
}
|
|
105
|
+
function hasValidRemoteShape(value) {
|
|
106
|
+
return isRecord(value)
|
|
107
|
+
&& (value.type === "streamable-http" || value.type === "sse")
|
|
108
|
+
&& hasValidHttpUrl(value.url);
|
|
109
|
+
}
|
|
110
|
+
function hasValidRegistryServerShape(server) {
|
|
111
|
+
return typeof server.$schema === "string"
|
|
112
|
+
&& OFFICIAL_SCHEMA_PATTERN.test(server.$schema)
|
|
113
|
+
&& isSafeServerName(typeof server.name === "string" ? server.name : undefined)
|
|
114
|
+
&& isSafeServerVersion(typeof server.version === "string" ? server.version : undefined)
|
|
115
|
+
&& typeof server.description === "string"
|
|
116
|
+
&& server.description.length >= 1
|
|
117
|
+
&& server.description.length <= 100
|
|
118
|
+
&& (server.packages === undefined || (Array.isArray(server.packages) && server.packages.every(hasValidPackageShape)))
|
|
119
|
+
&& (server.remotes === undefined || (Array.isArray(server.remotes) && server.remotes.every(hasValidRemoteShape)));
|
|
120
|
+
}
|
|
121
|
+
function matchingRegistryServer(response, serverName, serverVersion) {
|
|
122
|
+
const payload = responseJson(response);
|
|
123
|
+
if (response.statusCode !== 200 || !isRecord(payload) || !isRecord(payload.server)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const server = payload.server;
|
|
127
|
+
return hasValidRegistryServerShape(server)
|
|
128
|
+
&& server.name === serverName
|
|
129
|
+
&& (serverVersion === undefined || server.version === serverVersion);
|
|
130
|
+
}
|
|
131
|
+
function matchingNpmPackument(response, packageName, packageVersion, serverName) {
|
|
132
|
+
const payload = responseJson(response);
|
|
133
|
+
if (response.statusCode !== 200 || !isRecord(payload) || payload.name !== packageName || !isRecord(payload.versions)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const version = payload.versions[packageVersion];
|
|
137
|
+
return isRecord(version)
|
|
138
|
+
&& version.name === packageName
|
|
139
|
+
&& version.version === packageVersion
|
|
140
|
+
&& version.mcpName === serverName
|
|
141
|
+
&& isRecord(version.dist)
|
|
142
|
+
&& isIntegrity(version.dist.integrity);
|
|
143
|
+
}
|
|
144
|
+
function addFinding(findings, id, message) {
|
|
145
|
+
findings.push({ id, severity: "fail", message });
|
|
146
|
+
}
|
|
147
|
+
async function resolveServerJsonPath(targetPath) {
|
|
148
|
+
const resolved = path.resolve(targetPath);
|
|
149
|
+
try {
|
|
150
|
+
return (await stat(resolved)).isDirectory() ? path.join(resolved, "server.json") : resolved;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return resolved.endsWith(".json") ? resolved : path.join(resolved, "server.json");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function readJson(pathname) {
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(await readFile(pathname, "utf8"));
|
|
159
|
+
return isRecord(parsed) ? parsed : null;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function npmDeclarations(server) {
|
|
166
|
+
return Array.isArray(server?.packages)
|
|
167
|
+
? server.packages.filter((entry) => isRecord(entry) && entry.registryType === "npm")
|
|
168
|
+
: [];
|
|
169
|
+
}
|
|
170
|
+
function publisherPlan() {
|
|
171
|
+
return {
|
|
172
|
+
executable: false,
|
|
173
|
+
steps: [
|
|
174
|
+
{
|
|
175
|
+
order: 1,
|
|
176
|
+
command: "mcp-publisher login github",
|
|
177
|
+
purpose: "Authenticate with GitHub for Registry publication."
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
order: 2,
|
|
181
|
+
command: "mcp-publisher publish",
|
|
182
|
+
purpose: "Publish the validated Registry metadata."
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export async function buildMcpRegistryPublicationPreflight(targetPath, options = {}) {
|
|
188
|
+
void options;
|
|
189
|
+
const readiness = await buildMcpRegistryReadiness(targetPath);
|
|
190
|
+
const findings = readiness.findings.map(publicFinding);
|
|
191
|
+
const serverJsonPath = await resolveServerJsonPath(targetPath);
|
|
192
|
+
const server = await readJson(serverJsonPath);
|
|
193
|
+
const declarations = npmDeclarations(server);
|
|
194
|
+
const packageJson = await readJson(path.join(path.dirname(serverJsonPath), "package.json"));
|
|
195
|
+
if (declarations.length > 1) {
|
|
196
|
+
findings.push({
|
|
197
|
+
id: "registry.preflight.package.multiple-npm-declarations",
|
|
198
|
+
severity: "fail",
|
|
199
|
+
message: "Publication evidence supports exactly one npm package declaration."
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
else if (declarations.length === 1
|
|
203
|
+
&& typeof declarations[0].identifier === "string"
|
|
204
|
+
&& packageJson
|
|
205
|
+
&& packageJson.name !== declarations[0].identifier) {
|
|
206
|
+
findings.push({
|
|
207
|
+
id: "registry.preflight.package.local-name-mismatch",
|
|
208
|
+
severity: "fail",
|
|
209
|
+
message: "package.json name must match the declared npm package identifier."
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (declarations.length === 1 && !isSafeNpmIdentifier(declarations[0].identifier)) {
|
|
213
|
+
findings.push({
|
|
214
|
+
id: "registry.preflight.package.invalid-npm-identifier",
|
|
215
|
+
severity: "fail",
|
|
216
|
+
message: "The declared npm package identifier is not valid for public registry verification."
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const localReadiness = statusFromFindings(findings);
|
|
220
|
+
let packagePublication = declarations.length === 0
|
|
221
|
+
? "skipped"
|
|
222
|
+
: localReadiness === "fail" ? "fail" : "unknown";
|
|
223
|
+
let registryVersionAvailability = "unknown";
|
|
224
|
+
const declaration = declarations.length === 1 ? declarations[0] : undefined;
|
|
225
|
+
const serverName = isSafeServerName(readiness.serverName) ? readiness.serverName : undefined;
|
|
226
|
+
const serverVersion = isSafeServerVersion(readiness.serverVersion) ? readiness.serverVersion : undefined;
|
|
227
|
+
const packageName = isSafeNpmIdentifier(declaration?.identifier) ? declaration.identifier : undefined;
|
|
228
|
+
const declarationVersion = typeof declaration?.version === "string" ? declaration.version : undefined;
|
|
229
|
+
const packageVersion = isSafeServerVersion(declarationVersion)
|
|
230
|
+
? declarationVersion
|
|
231
|
+
: undefined;
|
|
232
|
+
const canVerifyNetwork = options.allowNetwork === true
|
|
233
|
+
&& localReadiness === "pass"
|
|
234
|
+
&& declaration !== undefined
|
|
235
|
+
&& serverName !== undefined
|
|
236
|
+
&& serverVersion !== undefined
|
|
237
|
+
&& packageName !== undefined
|
|
238
|
+
&& packageVersion !== undefined;
|
|
239
|
+
if (!canVerifyNetwork && localReadiness !== "fail") {
|
|
240
|
+
findings.push({
|
|
241
|
+
id: "registry.preflight.network-unverified",
|
|
242
|
+
severity: "warn",
|
|
243
|
+
message: "Package publication and Registry version availability require explicit network verification."
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (canVerifyNetwork && packageName && packageVersion && serverName && serverVersion) {
|
|
247
|
+
const request = options.request ?? requestBoundedHttp;
|
|
248
|
+
let npmResponse;
|
|
249
|
+
try {
|
|
250
|
+
npmResponse = await request(`${NPM_REGISTRY}/${encodeURIComponent(packageName)}`, REQUEST_OPTIONS);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
addFinding(findings, "registry.preflight.npm.request", "Public npm metadata could not be verified.");
|
|
254
|
+
packagePublication = "unknown";
|
|
255
|
+
return buildReport(readiness, localReadiness, packagePublication, registryVersionAvailability, findings);
|
|
256
|
+
}
|
|
257
|
+
if (npmResponse.statusCode !== 200 && npmResponse.statusCode !== 404) {
|
|
258
|
+
addFinding(findings, "registry.preflight.npm.response", "Public npm metadata could not prove package publication.");
|
|
259
|
+
packagePublication = "unknown";
|
|
260
|
+
return buildReport(readiness, localReadiness, packagePublication, registryVersionAvailability, findings);
|
|
261
|
+
}
|
|
262
|
+
if (!matchingNpmPackument(npmResponse, packageName, packageVersion, serverName)) {
|
|
263
|
+
addFinding(findings, "registry.preflight.npm.metadata", "Published npm metadata does not match the declared package version.");
|
|
264
|
+
packagePublication = "fail";
|
|
265
|
+
return buildReport(readiness, localReadiness, packagePublication, registryVersionAvailability, findings);
|
|
266
|
+
}
|
|
267
|
+
packagePublication = "pass";
|
|
268
|
+
let exactResponse;
|
|
269
|
+
try {
|
|
270
|
+
exactResponse = await request(`${MCP_REGISTRY}/v0.1/servers/${encodeURIComponent(serverName)}/versions/${encodeURIComponent(serverVersion)}`, REQUEST_OPTIONS);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
addFinding(findings, "registry.preflight.registry.exact-request", "Exact Registry version availability could not be verified.");
|
|
274
|
+
return buildReport(readiness, localReadiness, packagePublication, registryVersionAvailability, findings);
|
|
275
|
+
}
|
|
276
|
+
if (matchingRegistryServer(exactResponse, serverName, serverVersion)) {
|
|
277
|
+
registryVersionAvailability = "already-published";
|
|
278
|
+
addFinding(findings, "registry.preflight.registry.already-published", "The exact Registry version is already published and cannot be overwritten.");
|
|
279
|
+
}
|
|
280
|
+
else if (validNotFound(exactResponse)) {
|
|
281
|
+
let latestResponse;
|
|
282
|
+
try {
|
|
283
|
+
latestResponse = await request(`${MCP_REGISTRY}/v0.1/servers/${encodeURIComponent(serverName)}/versions/latest`, REQUEST_OPTIONS);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
addFinding(findings, "registry.preflight.registry.latest-request", "Latest Registry version availability could not be verified.");
|
|
287
|
+
return buildReport(readiness, localReadiness, packagePublication, registryVersionAvailability, findings);
|
|
288
|
+
}
|
|
289
|
+
if (validNotFound(latestResponse)) {
|
|
290
|
+
registryVersionAvailability = "available-first-publication";
|
|
291
|
+
}
|
|
292
|
+
else if (matchingRegistryServer(latestResponse, serverName) && responseJson(latestResponse) !== null) {
|
|
293
|
+
const payload = responseJson(latestResponse);
|
|
294
|
+
const latestVersion = payload.server.version;
|
|
295
|
+
if (latestVersion !== serverVersion) {
|
|
296
|
+
registryVersionAvailability = "available-new-version";
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
addFinding(findings, "registry.preflight.registry.latest-response", "Latest Registry metadata did not prove a different published version.");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
addFinding(findings, "registry.preflight.registry.latest-response", "Latest Registry metadata could not prove version availability.");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
addFinding(findings, "registry.preflight.registry.exact-response", "Exact Registry metadata could not prove version availability.");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return buildReport(readiness, localReadiness, packagePublication, registryVersionAvailability, findings);
|
|
311
|
+
}
|
|
312
|
+
function buildReport(readiness, localReadiness, packagePublication, registryVersionAvailability, findings) {
|
|
313
|
+
return {
|
|
314
|
+
schemaVersion: "1.0.0",
|
|
315
|
+
kind: "mcp-registry-publication-preflight",
|
|
316
|
+
generatedAt: new Date().toISOString(),
|
|
317
|
+
target: "server.json",
|
|
318
|
+
...(isSafeServerName(readiness.serverName) ? { serverName: readiness.serverName } : {}),
|
|
319
|
+
...(isSafeServerVersion(readiness.serverVersion) ? { serverVersion: readiness.serverVersion } : {}),
|
|
320
|
+
status: statusFromFindings(findings),
|
|
321
|
+
localReadiness,
|
|
322
|
+
packagePublication,
|
|
323
|
+
registryVersionAvailability,
|
|
324
|
+
publisherPlan: publisherPlan(),
|
|
325
|
+
findings
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
export function renderMcpRegistryPublicationPreflightJson(report) {
|
|
329
|
+
return JSON.stringify(report, null, 2);
|
|
330
|
+
}
|
|
331
|
+
export function renderMcpRegistryPublicationPreflight(report) {
|
|
332
|
+
const networkVerification = report.findings.some((finding) => finding.id === "registry.preflight.network-unverified")
|
|
333
|
+
? "NOT REQUESTED"
|
|
334
|
+
: report.packagePublication === "pass" && report.registryVersionAvailability !== "unknown"
|
|
335
|
+
? "COMPLETED"
|
|
336
|
+
: report.localReadiness === "fail"
|
|
337
|
+
? "NOT AVAILABLE"
|
|
338
|
+
: "INCOMPLETE";
|
|
339
|
+
const lines = [
|
|
340
|
+
`Registry publication preflight: ${report.status.toUpperCase()}`,
|
|
341
|
+
`Target: ${report.target}`,
|
|
342
|
+
report.serverName ? `Server: ${report.serverName}@${report.serverVersion ?? "unknown"}` : null,
|
|
343
|
+
`Local readiness: ${report.localReadiness.toUpperCase()}`,
|
|
344
|
+
`Package publication: ${report.packagePublication.toUpperCase()}`,
|
|
345
|
+
`Registry version availability: ${report.registryVersionAvailability.toUpperCase()}`,
|
|
346
|
+
`Network verification: ${networkVerification}`
|
|
347
|
+
].filter((line) => line !== null);
|
|
348
|
+
if (report.findings.length > 0) {
|
|
349
|
+
lines.push("", "Findings", "--------");
|
|
350
|
+
for (const finding of report.findings) {
|
|
351
|
+
lines.push(`${finding.severity.toUpperCase()} ${finding.id}: ${finding.message}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return lines.join("\n");
|
|
355
|
+
}
|
|
356
|
+
export function registryPublicationPreflightExitCode(report, requirePublishReady = false) {
|
|
357
|
+
return report.status === "fail" || (requirePublishReady && report.status !== "pass") ? 1 : 0;
|
|
358
|
+
}
|
|
@@ -9,6 +9,68 @@ const remoteTransportReliabilityStatusSchema = {
|
|
|
9
9
|
type: "string",
|
|
10
10
|
enum: ["pass", "warn", "fail", "skipped"]
|
|
11
11
|
};
|
|
12
|
+
const registryPublicationPreflightStatusSchema = {
|
|
13
|
+
type: "string",
|
|
14
|
+
enum: ["pass", "warn", "fail"]
|
|
15
|
+
};
|
|
16
|
+
const registryPackagePublicationSchema = {
|
|
17
|
+
type: "string",
|
|
18
|
+
enum: ["pass", "fail", "skipped", "unknown"]
|
|
19
|
+
};
|
|
20
|
+
const registryVersionAvailabilitySchema = {
|
|
21
|
+
type: "string",
|
|
22
|
+
enum: ["available-first-publication", "available-new-version", "already-published", "unknown"]
|
|
23
|
+
};
|
|
24
|
+
const registryPublisherPlanSchema = {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
executable: {
|
|
28
|
+
const: false
|
|
29
|
+
},
|
|
30
|
+
steps: {
|
|
31
|
+
type: "array",
|
|
32
|
+
minItems: 2,
|
|
33
|
+
maxItems: 2,
|
|
34
|
+
prefixItems: [
|
|
35
|
+
{
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
order: {
|
|
39
|
+
const: 1
|
|
40
|
+
},
|
|
41
|
+
command: {
|
|
42
|
+
const: "mcp-publisher login github"
|
|
43
|
+
},
|
|
44
|
+
purpose: {
|
|
45
|
+
type: "string"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
required: ["order", "command", "purpose"],
|
|
49
|
+
additionalProperties: false
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
order: {
|
|
55
|
+
const: 2
|
|
56
|
+
},
|
|
57
|
+
command: {
|
|
58
|
+
const: "mcp-publisher publish"
|
|
59
|
+
},
|
|
60
|
+
purpose: {
|
|
61
|
+
type: "string"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ["order", "command", "purpose"],
|
|
65
|
+
additionalProperties: false
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
items: false
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
required: ["executable", "steps"],
|
|
72
|
+
additionalProperties: false
|
|
73
|
+
};
|
|
12
74
|
const runtimeConformanceSchema = {
|
|
13
75
|
type: "object",
|
|
14
76
|
properties: {
|
|
@@ -198,6 +260,36 @@ const publicSchemaDefinitions = [
|
|
|
198
260
|
}
|
|
199
261
|
}
|
|
200
262
|
},
|
|
263
|
+
{
|
|
264
|
+
id: "doctor.registry.preflight.json",
|
|
265
|
+
command: "codex-plugin-doctor registry preflight <server.json|directory> --json",
|
|
266
|
+
outputKind: "mcp-registry-publication-preflight",
|
|
267
|
+
required: [
|
|
268
|
+
"schemaVersion",
|
|
269
|
+
"kind",
|
|
270
|
+
"generatedAt",
|
|
271
|
+
"target",
|
|
272
|
+
"status",
|
|
273
|
+
"localReadiness",
|
|
274
|
+
"packagePublication",
|
|
275
|
+
"registryVersionAvailability",
|
|
276
|
+
"publisherPlan",
|
|
277
|
+
"findings"
|
|
278
|
+
],
|
|
279
|
+
properties: {
|
|
280
|
+
target: {
|
|
281
|
+
const: "server.json"
|
|
282
|
+
},
|
|
283
|
+
status: registryPublicationPreflightStatusSchema,
|
|
284
|
+
localReadiness: registryPublicationPreflightStatusSchema,
|
|
285
|
+
packagePublication: registryPackagePublicationSchema,
|
|
286
|
+
registryVersionAvailability: registryVersionAvailabilitySchema,
|
|
287
|
+
publisherPlan: registryPublisherPlanSchema,
|
|
288
|
+
findings: {
|
|
289
|
+
type: "array"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
},
|
|
201
293
|
{
|
|
202
294
|
id: "doctor.audit.json",
|
|
203
295
|
command: "codex-plugin-doctor audit --installed --json",
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export { buildEcosystemAudit, renderEcosystemAudit, renderEcosystemAuditJson, ty
|
|
|
31
31
|
export { applyPolicyToDepAudit, applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames, type PolicyPackName } from "./policy/policy-packs.js";
|
|
32
32
|
export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson, type GenericMcpDoctorReport } from "./mcp/generic-mcp-doctor.js";
|
|
33
33
|
export { buildMcpRegistryReadiness, inspectMcpRegistryServer, registryReadinessExitCode, renderMcpRegistryReadiness, renderMcpRegistryReadinessJson, type InspectMcpRegistryOptions, type McpRegistryFinding, type McpRegistryInstallability, type McpRegistryReadinessReport, type McpRegistryScorecard } from "./core/mcp-registry.js";
|
|
34
|
+
export { buildMcpRegistryPublicationPreflight, registryPublicationPreflightExitCode, renderMcpRegistryPublicationPreflight, renderMcpRegistryPublicationPreflightJson, type BuildMcpRegistryPublicationPreflightOptions, type McpRegistryPublicationPreflightFinding, type McpRegistryPublicationPreflightReport, type McpRegistryPublicationPreflightStatus } from "./core/mcp-registry-preflight.js";
|
|
34
35
|
export { watchPlugin, type WatchPluginOptions, type WatchPluginResult } from "./core/watch-plugin.js";
|
|
35
36
|
export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepAuditJson, renderDepAuditSarif, type DepAuditRecommendation, type DepAuditReport, type DepAuditVulnerability } from "./core/dep-audit.js";
|
|
36
37
|
export { initGitHooks, removeGitHooks, type InitGitHooksResult, type RemoveGitHooksResult } from "./core/init-git-hooks.js";
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export { buildEcosystemAudit, renderEcosystemAudit, renderEcosystemAuditJson } f
|
|
|
31
31
|
export { applyPolicyToDepAudit, applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames } from "./policy/policy-packs.js";
|
|
32
32
|
export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson } from "./mcp/generic-mcp-doctor.js";
|
|
33
33
|
export { buildMcpRegistryReadiness, inspectMcpRegistryServer, registryReadinessExitCode, renderMcpRegistryReadiness, renderMcpRegistryReadinessJson } from "./core/mcp-registry.js";
|
|
34
|
+
export { buildMcpRegistryPublicationPreflight, registryPublicationPreflightExitCode, renderMcpRegistryPublicationPreflight, renderMcpRegistryPublicationPreflightJson } from "./core/mcp-registry-preflight.js";
|
|
34
35
|
export { watchPlugin } from "./core/watch-plugin.js";
|
|
35
36
|
export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepAuditJson, renderDepAuditSarif } from "./core/dep-audit.js";
|
|
36
37
|
export { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
|
package/dist/run-cli.js
CHANGED
|
@@ -29,6 +29,7 @@ import { buildDoctorReviewBundle, diffDoctorReviewBundles, renderDoctorReviewBun
|
|
|
29
29
|
import { buildDoctorReleaseEvidenceAssetReport, buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceAsset, renderDoctorReleaseEvidenceAssetJson, renderDoctorReleaseEvidence, renderDoctorReleaseEvidenceJson, renderDoctorReleaseEvidenceVerification, renderDoctorReleaseEvidenceVerificationJson, verifyDoctorReleaseEvidence } from "./core/release-evidence.js";
|
|
30
30
|
import { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson } from "./core/npm-package-doctor.js";
|
|
31
31
|
import { buildMcpRegistryReadiness, inspectMcpRegistryServer, registryReadinessExitCode, renderMcpRegistryReadiness, renderMcpRegistryReadinessJson } from "./core/mcp-registry.js";
|
|
32
|
+
import { buildMcpRegistryPublicationPreflight, registryPublicationPreflightExitCode, renderMcpRegistryPublicationPreflight, renderMcpRegistryPublicationPreflightJson } from "./core/mcp-registry-preflight.js";
|
|
32
33
|
import { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRiskDiffReportJson } from "./core/risk-diff.js";
|
|
33
34
|
import { buildDoctorInspectorReport, renderDoctorInspectorReport, renderDoctorInspectorReportJson } from "./core/inspector-bridge.js";
|
|
34
35
|
import { applyFixPlan, buildFixPlan, renderApplyFixResult, renderFixPlanJsonReport, renderFixPlan } from "./core/fix-plan.js";
|
|
@@ -169,7 +170,8 @@ function parseRemoteNetworkFlags(flags, runtime) {
|
|
|
169
170
|
function printUsage(io) {
|
|
170
171
|
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--runtime [--allow-network [--allow-local-network]]] [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus [--manifest <corpus.json>] [--json] [--output <path>]|corpus metrics --manifest <corpus.json> [--json|--markdown] [--output <path>] [--min-precision <0..1>] [--min-recall <0..1>] [--max-false-positive-rate <0..1>]|runtime-plan <path> [--sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--sandbox docker] [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>] [--failures-only]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>] [--upload]|mcp <path> [--runtime [--allow-network [--allow-local-network]]]|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor init-git-hooks [path] [--force] [--json]\n codex-plugin-doctor init-git-hooks [path] --remove [--json]\n codex-plugin-doctor completion bash|zsh|fish\n codex-plugin-doctor config validate <path> [--json]\n codex-plugin-doctor release check <path> [--json] [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
|
|
171
172
|
io.writeStderr("Registry readiness: codex-plugin-doctor registry check <server.json|directory> [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
172
|
-
+ " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]"
|
|
173
|
+
+ " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
174
|
+
+ "Registry publication preflight: codex-plugin-doctor registry preflight <server.json|directory> [--allow-network] [--json] [--output <path>] [--require-publish-ready]");
|
|
173
175
|
io.writeStderr("Corpus quality regression: codex-plugin-doctor doctor corpus metrics diff --before <metrics.json> --after <metrics.json> [--fail-on-regression] [--json|--markdown] [--output <path>]");
|
|
174
176
|
io.writeStderr("Suppression governance: codex-plugin-doctor suppress list <path> [--fail-on-expired] [--fail-on-invalid] [--warn-expiring-within-days <days>]\n codex-plugin-doctor suppress prune <path> [--apply] [--json]");
|
|
175
177
|
io.writeStderr("Baseline gating: codex-plugin-doctor baseline create <path> --output <path> [--runtime]\n codex-plugin-doctor check <path> --baseline <path>");
|
|
@@ -2363,16 +2365,18 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2363
2365
|
const subcommand = maybePath;
|
|
2364
2366
|
const target = remainingArgs[0];
|
|
2365
2367
|
const flags = remainingArgs.slice(1);
|
|
2366
|
-
if ((subcommand !== "check" && subcommand !== "inspect") || !target || target.startsWith("--")) {
|
|
2368
|
+
if ((subcommand !== "check" && subcommand !== "inspect" && subcommand !== "preflight") || !target || target.startsWith("--")) {
|
|
2367
2369
|
io.writeStderr("Usage: codex-plugin-doctor registry check <server.json|directory> [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
2368
|
-
+ " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]"
|
|
2370
|
+
+ " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
2371
|
+
+ " codex-plugin-doctor registry preflight <server.json|directory> [--allow-network] [--json] [--output <path>] [--require-publish-ready]");
|
|
2369
2372
|
return 2;
|
|
2370
2373
|
}
|
|
2371
2374
|
const allowedFlags = new Set([
|
|
2372
2375
|
"--json",
|
|
2373
2376
|
"--output",
|
|
2374
2377
|
"--allow-network",
|
|
2375
|
-
"--require-registry-readiness"
|
|
2378
|
+
"--require-registry-readiness",
|
|
2379
|
+
"--require-publish-ready"
|
|
2376
2380
|
]);
|
|
2377
2381
|
let outputPath = null;
|
|
2378
2382
|
for (let index = 0; index < flags.length; index += 1) {
|
|
@@ -2393,14 +2397,33 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2393
2397
|
}
|
|
2394
2398
|
const allowNetwork = flags.includes("--allow-network");
|
|
2395
2399
|
if (subcommand === "check" && allowNetwork) {
|
|
2396
|
-
io.writeStderr("--allow-network is supported
|
|
2400
|
+
io.writeStderr("--allow-network is not supported by registry check.");
|
|
2397
2401
|
return 2;
|
|
2398
2402
|
}
|
|
2399
2403
|
if (subcommand === "inspect" && !allowNetwork) {
|
|
2400
2404
|
io.writeStderr("registry inspect requires explicit --allow-network consent.");
|
|
2401
2405
|
return 2;
|
|
2402
2406
|
}
|
|
2407
|
+
if (subcommand === "preflight" && flags.includes("--require-registry-readiness")) {
|
|
2408
|
+
io.writeStderr("--require-registry-readiness is supported only by registry check or registry inspect.");
|
|
2409
|
+
return 2;
|
|
2410
|
+
}
|
|
2411
|
+
if (subcommand !== "preflight" && flags.includes("--require-publish-ready")) {
|
|
2412
|
+
io.writeStderr("--require-publish-ready is supported only by registry preflight.");
|
|
2413
|
+
return 2;
|
|
2414
|
+
}
|
|
2403
2415
|
try {
|
|
2416
|
+
if (subcommand === "preflight") {
|
|
2417
|
+
const report = await buildMcpRegistryPublicationPreflight(target, { allowNetwork });
|
|
2418
|
+
const rendered = flags.includes("--json")
|
|
2419
|
+
? renderMcpRegistryPublicationPreflightJson(report)
|
|
2420
|
+
: renderMcpRegistryPublicationPreflight(report);
|
|
2421
|
+
if (outputPath) {
|
|
2422
|
+
await writeFile(outputPath, rendered, "utf8");
|
|
2423
|
+
}
|
|
2424
|
+
io.writeStdout(rendered);
|
|
2425
|
+
return registryPublicationPreflightExitCode(report, flags.includes("--require-publish-ready"));
|
|
2426
|
+
}
|
|
2404
2427
|
const report = subcommand === "check"
|
|
2405
2428
|
? await buildMcpRegistryReadiness(target)
|
|
2406
2429
|
: await inspectMcpRegistryServer(target, { allowNetwork: true });
|
|
@@ -2414,7 +2437,12 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2414
2437
|
return registryReadinessExitCode(report, flags.includes("--require-registry-readiness"));
|
|
2415
2438
|
}
|
|
2416
2439
|
catch (error) {
|
|
2417
|
-
|
|
2440
|
+
if (subcommand === "preflight") {
|
|
2441
|
+
io.writeStderr("Registry publication preflight failed.");
|
|
2442
|
+
}
|
|
2443
|
+
else {
|
|
2444
|
+
io.writeStderr(`Registry inspection failed: ${error.message}`);
|
|
2445
|
+
}
|
|
2418
2446
|
return 1;
|
|
2419
2447
|
}
|
|
2420
2448
|
}
|
package/package.json
CHANGED