@youdie006/prodex 0.2.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/LICENSE +21 -0
- package/README.md +418 -0
- package/dist/banner.d.ts +5 -0
- package/dist/banner.js +47 -0
- package/dist/banner.js.map +1 -0
- package/dist/bundle.d.ts +15 -0
- package/dist/bundle.js +30 -0
- package/dist/bundle.js.map +1 -0
- package/dist/chatgpt-browser.d.ts +119 -0
- package/dist/chatgpt-browser.js +857 -0
- package/dist/chatgpt-browser.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +3502 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.js +277 -0
- package/dist/config.js.map +1 -0
- package/dist/http-mcp.d.ts +20 -0
- package/dist/http-mcp.js +236 -0
- package/dist/http-mcp.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-tools.d.ts +395 -0
- package/dist/mcp-tools.js +167 -0
- package/dist/mcp-tools.js.map +1 -0
- package/dist/mcp.d.ts +37 -0
- package/dist/mcp.js +229 -0
- package/dist/mcp.js.map +1 -0
- package/dist/repo-write.d.ts +47 -0
- package/dist/repo-write.js +427 -0
- package/dist/repo-write.js.map +1 -0
- package/dist/repo.d.ts +27 -0
- package/dist/repo.js +386 -0
- package/dist/repo.js.map +1 -0
- package/dist/safe-file.d.ts +25 -0
- package/dist/safe-file.js +295 -0
- package/dist/safe-file.js.map +1 -0
- package/dist/schema.d.ts +402 -0
- package/dist/schema.js +109 -0
- package/dist/schema.js.map +1 -0
- package/dist/store.d.ts +157 -0
- package/dist/store.js +1402 -0
- package/dist/store.js.map +1 -0
- package/docs/claude.md +130 -0
- package/docs/clients.md +75 -0
- package/docs/http-mcp.md +223 -0
- package/package.json +67 -0
- package/scripts/release-check.mjs +436 -0
- package/scripts/release-pack.mjs +481 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const RESERVED_PACKAGE_NAMES = new Set(["node_modules", "favicon.ico"]);
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const args = parseArgs(process.argv.slice(2));
|
|
14
|
+
const root = args.root ?? repoRoot;
|
|
15
|
+
const metadataOnly = args.metadataOnly;
|
|
16
|
+
const verificationOnly = args.verificationOnly;
|
|
17
|
+
if (metadataOnly && verificationOnly) {
|
|
18
|
+
throw new Error("release check flags failed: --metadata-only and --verification-only cannot be combined");
|
|
19
|
+
}
|
|
20
|
+
if (!verificationOnly) {
|
|
21
|
+
await checkReleaseMetadata(root);
|
|
22
|
+
console.log("release_metadata=ok");
|
|
23
|
+
}
|
|
24
|
+
if (!metadataOnly) {
|
|
25
|
+
await runFullReleaseVerification(root);
|
|
26
|
+
console.log("release_verification=ok");
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function checkReleaseMetadata(rootDir) {
|
|
34
|
+
const packageJsonPath = path.join(rootDir, "package.json");
|
|
35
|
+
const packageJson = parseReleasePackageJson(await readRequiredPackageJson(packageJsonPath), packageJsonPath);
|
|
36
|
+
const identityError = packageIdentityError(packageJson);
|
|
37
|
+
if (identityError) throw new Error(`release metadata failed: ${identityError}`);
|
|
38
|
+
if (packageJson.private === true) {
|
|
39
|
+
throw new Error("release metadata failed: package.json private: true prevents npm publish");
|
|
40
|
+
}
|
|
41
|
+
if (typeof packageJson.license !== "string" || packageJson.license.trim() === "") {
|
|
42
|
+
throw new Error("release metadata failed: package.json must include an explicit license before publishing");
|
|
43
|
+
}
|
|
44
|
+
const license = packageJson.license.trim();
|
|
45
|
+
if (license === "UNLICENSED") {
|
|
46
|
+
throw new Error('release metadata failed: license "UNLICENSED" is not publishable');
|
|
47
|
+
}
|
|
48
|
+
if (license !== "MIT") {
|
|
49
|
+
throw new Error("release metadata failed: package.json license must be MIT before publishing");
|
|
50
|
+
}
|
|
51
|
+
await assertRegularLicenseFile(rootDir, license);
|
|
52
|
+
await assertPackedFileModes(rootDir, packageJson);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseReleasePackageJson(raw, packageJsonPath) {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(raw);
|
|
58
|
+
} catch {
|
|
59
|
+
throw new Error(`release metadata failed: package.json is not valid JSON at ${packageJsonPath}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function packageIdentityError(packageJson) {
|
|
64
|
+
if (!isNonEmptyString(packageJson?.name) || !isNonEmptyString(packageJson?.version)) {
|
|
65
|
+
return "package.json must include non-empty string name and version";
|
|
66
|
+
}
|
|
67
|
+
if (!isNpmPublishablePackageName(packageJson.name)) {
|
|
68
|
+
return "package.json name must be npm-publishable";
|
|
69
|
+
}
|
|
70
|
+
if (!isValidSemverVersion(packageJson.version)) {
|
|
71
|
+
return "package.json version must be valid semver";
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isNonEmptyString(value) {
|
|
77
|
+
return typeof value === "string" && value.trim() !== "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isNpmPublishablePackageName(value) {
|
|
81
|
+
if (!isNonEmptyString(value) || value.length > 214 || value !== value.toLowerCase()) return false;
|
|
82
|
+
if (RESERVED_PACKAGE_NAMES.has(value)) return false;
|
|
83
|
+
if (value.startsWith("@")) {
|
|
84
|
+
const parts = value.slice(1).split("/");
|
|
85
|
+
return parts.length === 2 && parts.every(isPackageNameSegment);
|
|
86
|
+
}
|
|
87
|
+
return !value.includes("/") && isPackageNameSegment(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isPackageNameSegment(value) {
|
|
91
|
+
return /^(?![._])[a-z0-9][a-z0-9._~-]*$/.test(value);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isValidSemverVersion(value) {
|
|
95
|
+
if (!isNonEmptyString(value)) return false;
|
|
96
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function assertRegularLicenseFile(rootDir, expectedLicense) {
|
|
100
|
+
const licensePath = path.join(rootDir, "LICENSE");
|
|
101
|
+
try {
|
|
102
|
+
const stat = await lstat(licensePath);
|
|
103
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
104
|
+
throw new Error("release metadata failed: LICENSE must be a regular file and must not be a symlink");
|
|
105
|
+
}
|
|
106
|
+
if (stat.nlink > 1) {
|
|
107
|
+
throw new Error("release metadata failed: LICENSE must not have hard links. replace LICENSE with a non-hard-linked regular file, then rerun release:check.");
|
|
108
|
+
}
|
|
109
|
+
const raw = await readFile(licensePath, "utf8");
|
|
110
|
+
if (expectedLicense === "MIT" && !isMitLicenseText(raw)) {
|
|
111
|
+
throw new Error("release metadata failed: LICENSE content must match package.json license MIT");
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (isMissingFileError(error)) {
|
|
115
|
+
throw new Error("release metadata failed: publishable packages must include a LICENSE file");
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isMitLicenseText(raw) {
|
|
122
|
+
return /\bMIT License\b/.test(raw);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function readRequiredPackageJson(packageJsonPath) {
|
|
126
|
+
try {
|
|
127
|
+
return await readFile(packageJsonPath, "utf8");
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (isMissingFileError(error)) {
|
|
130
|
+
throw new Error(`release metadata failed: package.json not found at ${packageJsonPath}`);
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function runFullReleaseVerification(rootDir) {
|
|
137
|
+
const checks = [
|
|
138
|
+
["npm", ["test"]],
|
|
139
|
+
["npm", ["run", "typecheck"]],
|
|
140
|
+
["npm", ["run", "build"]],
|
|
141
|
+
["npm", ["run", "smoke:package"]],
|
|
142
|
+
["node", ["dist/cli.js", "doctor"]]
|
|
143
|
+
];
|
|
144
|
+
for (const [command, commandArgs] of checks) {
|
|
145
|
+
await run(commandForPlatform(command), commandArgs, rootDir);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function assertPackedFileModes(rootDir, packageJson) {
|
|
150
|
+
const packedFiles = await readPackedFiles(rootDir);
|
|
151
|
+
const nonRegular = await findNonRegularPackedFiles(rootDir, packedFiles);
|
|
152
|
+
if (nonRegular.length > 0) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`release metadata failed: packed files must be regular non-symlink files: ${formatPathList(nonRegular)}. ` +
|
|
155
|
+
"replace them with regular files, then rerun release:check."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const nonExecutableSourceBins = await findNonExecutableBinSourceFiles(rootDir, packageJson);
|
|
159
|
+
if (nonExecutableSourceBins.length > 0) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`release metadata failed: package bin entries must be executable: ${formatPathList(nonExecutableSourceBins)}. ` +
|
|
162
|
+
"restore executable mode on package bin files, then rerun release:check."
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const invalid = findExecutableNonBinPackedFiles(packedFiles, packageJson);
|
|
166
|
+
if (invalid.length > 0) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`release metadata failed: packed files have unexpected executable modes outside package bin entries: ${formatPathList(invalid)}. ` +
|
|
169
|
+
"fix file modes or publish from a filesystem that preserves executable bits, then rerun release:check."
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const nonExecutablePackedBins = findNonExecutableBinPackedFiles(packedFiles, packageJson);
|
|
173
|
+
if (nonExecutablePackedBins.length > 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`release metadata failed: package bin entries must be executable: ${formatPathList(nonExecutablePackedBins)}. ` +
|
|
176
|
+
"restore executable mode on package bin files, then rerun release:check."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const hardLinked = await findHardLinkedPackedFiles(rootDir, packedFiles);
|
|
180
|
+
if (hardLinked.length > 0) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`release metadata failed: packed files must not have hard links: ${formatPathList(hardLinked)}. ` +
|
|
183
|
+
"replace hard-linked packed files with independent files, then rerun release:check."
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function readPackedFiles(rootDir) {
|
|
189
|
+
let stdout;
|
|
190
|
+
try {
|
|
191
|
+
({ stdout } = await execFileAsync(commandForPlatform("npm"), ["pack", "--json", "--dry-run", "--ignore-scripts"], {
|
|
192
|
+
cwd: rootDir,
|
|
193
|
+
timeout: 120_000,
|
|
194
|
+
maxBuffer: 20 * 1024 * 1024
|
|
195
|
+
}));
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw new Error(`release metadata failed: npm pack dry-run failed: ${commandFailureDetail(error)}`);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const entries = JSON.parse(stdout);
|
|
201
|
+
const files = entries?.[0]?.files;
|
|
202
|
+
if (!Array.isArray(files)) {
|
|
203
|
+
throw new Error("release metadata failed: npm pack dry-run did not return a file list");
|
|
204
|
+
}
|
|
205
|
+
for (const file of files) {
|
|
206
|
+
if (typeof file?.path !== "string" || file.path.trim() === "") {
|
|
207
|
+
throw new Error("release metadata failed: npm pack dry-run file entry is missing a path");
|
|
208
|
+
}
|
|
209
|
+
if (typeof file.mode !== "number") {
|
|
210
|
+
throw new Error(`release metadata failed: npm pack dry-run file entry is missing mode metadata: ${normalizePackagePath(file.path)}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return files;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error instanceof Error && error.message.startsWith("release metadata failed:")) {
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
throw new Error("release metadata failed: npm pack dry-run did not return valid JSON");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function findNonRegularPackedFiles(rootDir, files) {
|
|
223
|
+
const invalid = [];
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
if (typeof file?.path !== "string") continue;
|
|
226
|
+
const packagePath = normalizePackagePath(file.path);
|
|
227
|
+
const filePath = path.join(rootDir, packagePath);
|
|
228
|
+
const relative = path.relative(rootDir, filePath);
|
|
229
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
230
|
+
invalid.push(packagePath);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const stat = await lstat(filePath);
|
|
235
|
+
if (stat.isSymbolicLink() || !stat.isFile()) invalid.push(packagePath);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (isMissingFileError(error)) invalid.push(packagePath);
|
|
238
|
+
else throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return invalid;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function findExecutableNonBinPackedFiles(files, packageJson) {
|
|
245
|
+
const binPaths = packageBinPaths(packageJson);
|
|
246
|
+
return files
|
|
247
|
+
.filter((file) => typeof file?.path === "string" && typeof file?.mode === "number")
|
|
248
|
+
.filter((file) => (file.mode & 0o111) !== 0)
|
|
249
|
+
.map((file) => normalizePackagePath(file.path))
|
|
250
|
+
.filter((filePath) => !binPaths.has(filePath));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function findNonExecutableBinPackedFiles(files, packageJson) {
|
|
254
|
+
const filesByPath = new Map(
|
|
255
|
+
files
|
|
256
|
+
.filter((file) => typeof file?.path === "string")
|
|
257
|
+
.map((file) => [normalizePackagePath(file.path), file])
|
|
258
|
+
);
|
|
259
|
+
return [...packageBinPaths(packageJson)].filter((filePath) => {
|
|
260
|
+
const file = filesByPath.get(filePath);
|
|
261
|
+
return !file || typeof file.mode !== "number" || (file.mode & 0o111) === 0;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function findNonExecutableBinSourceFiles(rootDir, packageJson) {
|
|
266
|
+
const invalid = [];
|
|
267
|
+
for (const packagePath of packageBinPaths(packageJson)) {
|
|
268
|
+
const filePath = path.join(rootDir, packagePath);
|
|
269
|
+
const relative = path.relative(rootDir, filePath);
|
|
270
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
271
|
+
invalid.push(packagePath);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const stat = await lstat(filePath);
|
|
276
|
+
if ((stat.mode & 0o111) === 0) invalid.push(packagePath);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (isMissingFileError(error)) invalid.push(packagePath);
|
|
279
|
+
else throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return invalid;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function findHardLinkedPackedFiles(rootDir, files) {
|
|
286
|
+
const invalid = [];
|
|
287
|
+
for (const file of files) {
|
|
288
|
+
if (typeof file?.path !== "string") continue;
|
|
289
|
+
const packagePath = normalizePackagePath(file.path);
|
|
290
|
+
const filePath = path.join(rootDir, packagePath);
|
|
291
|
+
const relative = path.relative(rootDir, filePath);
|
|
292
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
293
|
+
invalid.push(packagePath);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const stat = await lstat(filePath);
|
|
298
|
+
if (stat.nlink > 1) invalid.push(packagePath);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (isMissingFileError(error)) invalid.push(packagePath);
|
|
301
|
+
else throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return invalid;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function packageBinPaths(packageJson) {
|
|
308
|
+
const paths = new Set();
|
|
309
|
+
const bin = packageJson?.bin;
|
|
310
|
+
if (typeof bin === "string") {
|
|
311
|
+
paths.add(normalizePackagePath(bin));
|
|
312
|
+
} else if (bin && typeof bin === "object") {
|
|
313
|
+
for (const value of Object.values(bin)) {
|
|
314
|
+
if (typeof value === "string") paths.add(normalizePackagePath(value));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return paths;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function normalizePackagePath(value) {
|
|
321
|
+
return value.replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function formatPathList(paths) {
|
|
325
|
+
const shown = paths.slice(0, 8).join(", ");
|
|
326
|
+
return paths.length > 8 ? `${shown}, ... (${paths.length} files)` : shown;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function run(command, commandArgs, cwd) {
|
|
330
|
+
const commandLine = [command, ...commandArgs].join(" ");
|
|
331
|
+
console.log(`release_check: ${commandLine}`);
|
|
332
|
+
try {
|
|
333
|
+
await execFileAsync(command, commandArgs, {
|
|
334
|
+
cwd,
|
|
335
|
+
timeout: 180_000,
|
|
336
|
+
maxBuffer: 20 * 1024 * 1024
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
throw new Error(`release verification failed: ${commandLine}: ${commandFailureDetail(error)}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function commandForPlatform(command) {
|
|
344
|
+
return process.platform === "win32" && command === "npm" ? "npm.cmd" : command;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function parseArgs(values) {
|
|
348
|
+
const parsed = {
|
|
349
|
+
root: undefined,
|
|
350
|
+
metadataOnly: false,
|
|
351
|
+
verificationOnly: false
|
|
352
|
+
};
|
|
353
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
354
|
+
const arg = values[index];
|
|
355
|
+
if (arg === "--metadata-only") {
|
|
356
|
+
parsed.metadataOnly = true;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (arg === "--verification-only") {
|
|
360
|
+
parsed.verificationOnly = true;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (arg === "--root") {
|
|
364
|
+
const value = values[index + 1];
|
|
365
|
+
if (!value || value.startsWith("-")) {
|
|
366
|
+
throw new Error("release check flags failed: --root requires a value");
|
|
367
|
+
}
|
|
368
|
+
parsed.root = value;
|
|
369
|
+
index += 1;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (arg.startsWith("-")) {
|
|
373
|
+
throw new Error(`release check flags failed: ${unknownOptionMessage(arg, ["--root", "--metadata-only", "--verification-only"])}`);
|
|
374
|
+
}
|
|
375
|
+
throw new Error(`release check flags failed: unexpected argument ${arg}`);
|
|
376
|
+
}
|
|
377
|
+
return parsed;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function unknownOptionMessage(option, candidates) {
|
|
381
|
+
const suggestion = closestFlagSuggestion(option, candidates);
|
|
382
|
+
return `unknown option ${option}${suggestion ? `. Did you mean \`${suggestion}\`?` : ""}`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function closestFlagSuggestion(option, candidates) {
|
|
386
|
+
let best;
|
|
387
|
+
for (const candidate of candidates) {
|
|
388
|
+
const distance = editDistance(option, candidate);
|
|
389
|
+
const prefixMatch = candidate.startsWith(option);
|
|
390
|
+
if (!best || prefixMatch || distance < best.distance) {
|
|
391
|
+
best = { candidate, distance, prefixMatch };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return best && (best.prefixMatch || best.distance <= 2) ? best.candidate : undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function editDistance(left, right) {
|
|
398
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
399
|
+
const current = Array.from({ length: right.length + 1 }, () => 0);
|
|
400
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
401
|
+
current[0] = leftIndex;
|
|
402
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
403
|
+
const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
404
|
+
current[rightIndex] = Math.min(
|
|
405
|
+
previous[rightIndex] + 1,
|
|
406
|
+
current[rightIndex - 1] + 1,
|
|
407
|
+
previous[rightIndex - 1] + substitutionCost
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
previous.splice(0, previous.length, ...current);
|
|
411
|
+
}
|
|
412
|
+
return previous[right.length];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function isMissingFileError(error) {
|
|
416
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function commandFailureDetail(error) {
|
|
420
|
+
const failed = error && typeof error === "object" ? error : {};
|
|
421
|
+
const stderr = firstOutputLine(failed.stderr);
|
|
422
|
+
if (stderr) return stderr;
|
|
423
|
+
const stdout = firstOutputLine(failed.stdout);
|
|
424
|
+
if (stdout) return stdout;
|
|
425
|
+
if (typeof failed.code === "number") return `exit code ${failed.code}`;
|
|
426
|
+
if (typeof failed.signal === "string" && failed.signal) return `signal ${failed.signal}`;
|
|
427
|
+
return "failed without output";
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function firstOutputLine(value) {
|
|
431
|
+
if (typeof value !== "string") return undefined;
|
|
432
|
+
return value
|
|
433
|
+
.split(/\r?\n/)
|
|
434
|
+
.map((line) => line.trim())
|
|
435
|
+
.find(Boolean);
|
|
436
|
+
}
|