@openpkg-ts/cli 0.12.0 → 0.12.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/dist/index.js +200 -99
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs2 from "node:fs";
|
|
5
|
+
import path2 from "node:path";
|
|
6
6
|
import readline from "node:readline/promises";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
8
|
import {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
extractSpec,
|
|
14
14
|
getAvailableVersions,
|
|
15
15
|
getValidationErrors,
|
|
16
|
+
isPathLikeInput,
|
|
16
17
|
isRemoteInput,
|
|
17
18
|
listExports,
|
|
18
19
|
loadConfig,
|
|
@@ -21,6 +22,78 @@ import {
|
|
|
21
22
|
recommendSemverBump,
|
|
22
23
|
resolveTarget
|
|
23
24
|
} from "@openpkg-ts/sdk";
|
|
25
|
+
|
|
26
|
+
// src/env.ts
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
function parseEnvText(content) {
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
32
|
+
let line = rawLine.trim();
|
|
33
|
+
if (!line || line.startsWith("#"))
|
|
34
|
+
continue;
|
|
35
|
+
if (line.startsWith("export "))
|
|
36
|
+
line = line.slice(7).trimStart();
|
|
37
|
+
const eq = line.indexOf("=");
|
|
38
|
+
if (eq <= 0)
|
|
39
|
+
continue;
|
|
40
|
+
const key = line.slice(0, eq).trim();
|
|
41
|
+
if (!key || /\s/.test(key))
|
|
42
|
+
continue;
|
|
43
|
+
let value = line.slice(eq + 1);
|
|
44
|
+
if (value.startsWith(" ") || value.startsWith("\t"))
|
|
45
|
+
value = value.trimStart();
|
|
46
|
+
if (value.startsWith('"')) {
|
|
47
|
+
let i = 1;
|
|
48
|
+
let parsed = "";
|
|
49
|
+
while (i < value.length) {
|
|
50
|
+
const ch = value[i];
|
|
51
|
+
if (ch === "\\" && i + 1 < value.length) {
|
|
52
|
+
const next = value[i + 1];
|
|
53
|
+
parsed += next === "n" ? `
|
|
54
|
+
` : next === "r" ? "\r" : next === "t" ? "\t" : next;
|
|
55
|
+
i += 2;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (ch === '"')
|
|
59
|
+
break;
|
|
60
|
+
parsed += ch;
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
out[key] = parsed;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (value.startsWith("'")) {
|
|
67
|
+
const end = value.indexOf("'", 1);
|
|
68
|
+
out[key] = end === -1 ? value.slice(1) : value.slice(1, end);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const hash = value.search(/\s+#/);
|
|
72
|
+
out[key] = (hash === -1 ? value : value.slice(0, hash)).trim();
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function parseIfExists(file) {
|
|
77
|
+
try {
|
|
78
|
+
if (!fs.existsSync(file))
|
|
79
|
+
return {};
|
|
80
|
+
return parseEnvText(fs.readFileSync(file, "utf8"));
|
|
81
|
+
} catch {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function loadCwdEnv(cwd = process.cwd()) {
|
|
86
|
+
const parsed = {
|
|
87
|
+
...parseIfExists(path.join(cwd, ".env")),
|
|
88
|
+
...parseIfExists(path.join(cwd, ".env.local"))
|
|
89
|
+
};
|
|
90
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
91
|
+
if (process.env[k] === undefined)
|
|
92
|
+
process.env[k] = v;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/index.ts
|
|
24
97
|
var HELP = `openpkg - extract TypeScript API specs and generate docs
|
|
25
98
|
|
|
26
99
|
Usage:
|
|
@@ -56,21 +129,31 @@ cwd. Flags override the file. Example:
|
|
|
56
129
|
{ "followExternal": ["@ai-sdk/*"] }
|
|
57
130
|
{ "followExternal": "auto", "decisions": "jev" }
|
|
58
131
|
`;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
132
|
+
|
|
133
|
+
class CliError extends Error {
|
|
134
|
+
exitCode;
|
|
135
|
+
printed;
|
|
136
|
+
constructor(message, exitCode = 1, printed = false) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.exitCode = exitCode;
|
|
139
|
+
this.printed = printed;
|
|
140
|
+
this.name = "CliError";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function fail(message, exitCode = 1) {
|
|
144
|
+
throw new CliError(message, exitCode);
|
|
62
145
|
}
|
|
63
146
|
function write(content, output) {
|
|
64
147
|
if (output) {
|
|
65
|
-
|
|
66
|
-
|
|
148
|
+
fs2.mkdirSync(path2.dirname(path2.resolve(output)), { recursive: true });
|
|
149
|
+
fs2.writeFileSync(output, content);
|
|
67
150
|
console.error(`wrote ${output}`);
|
|
68
151
|
} else {
|
|
69
152
|
console.log(content);
|
|
70
153
|
}
|
|
71
154
|
}
|
|
72
155
|
function version() {
|
|
73
|
-
const pkg = JSON.parse(
|
|
156
|
+
const pkg = JSON.parse(fs2.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
74
157
|
return pkg.version;
|
|
75
158
|
}
|
|
76
159
|
function reportDiagnostics(diagnostics) {
|
|
@@ -80,7 +163,7 @@ function reportDiagnostics(diagnostics) {
|
|
|
80
163
|
}
|
|
81
164
|
}
|
|
82
165
|
if (diagnostics.some((d) => d.severity === "error")) {
|
|
83
|
-
|
|
166
|
+
throw new CliError("", 1, true);
|
|
84
167
|
}
|
|
85
168
|
}
|
|
86
169
|
function parseTargetArgs(positionals, cwd) {
|
|
@@ -88,18 +171,18 @@ function parseTargetArgs(positionals, cwd) {
|
|
|
88
171
|
return { input: cwd };
|
|
89
172
|
const first = positionals[0];
|
|
90
173
|
const rest = positionals.slice(1).join(" ").trim();
|
|
91
|
-
const abs = path.resolve(cwd, first);
|
|
92
174
|
if (isRemoteInput(first)) {
|
|
93
175
|
return { input: first, ...rest ? { intent: rest } : {} };
|
|
94
176
|
}
|
|
95
|
-
|
|
177
|
+
const abs = path2.resolve(cwd, first);
|
|
178
|
+
if (fs2.existsSync(abs) || isPathLikeInput(first)) {
|
|
96
179
|
return { input: abs, ...rest ? { intent: rest } : {} };
|
|
97
180
|
}
|
|
98
181
|
return { input: cwd, intent: positionals.join(" ") };
|
|
99
182
|
}
|
|
100
183
|
function formatPackages(candidates, cwd) {
|
|
101
184
|
return candidates.map((c, i) => {
|
|
102
|
-
const rel =
|
|
185
|
+
const rel = path2.relative(cwd, c.dir) || ".";
|
|
103
186
|
return ` ${i + 1}. ${c.name} ${rel}`;
|
|
104
187
|
}).join(`
|
|
105
188
|
`);
|
|
@@ -124,54 +207,44 @@ re-run with a path or intent, e.g. openpkg spec . ${hint}`);
|
|
|
124
207
|
rl.close();
|
|
125
208
|
}
|
|
126
209
|
}
|
|
127
|
-
function loadCwdEnv() {
|
|
128
|
-
for (const name of [".env.local", ".env"]) {
|
|
129
|
-
const file = path.join(process.cwd(), name);
|
|
130
|
-
if (!fs.existsSync(file))
|
|
131
|
-
continue;
|
|
132
|
-
for (const line of fs.readFileSync(file, "utf8").split(`
|
|
133
|
-
`)) {
|
|
134
|
-
const t = line.trim();
|
|
135
|
-
if (!t || t.startsWith("#"))
|
|
136
|
-
continue;
|
|
137
|
-
const i = t.indexOf("=");
|
|
138
|
-
if (i === -1)
|
|
139
|
-
continue;
|
|
140
|
-
const k = t.slice(0, i).trim();
|
|
141
|
-
const v = t.slice(i + 1).trim();
|
|
142
|
-
if (k && process.env[k] === undefined)
|
|
143
|
-
process.env[k] = v;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
210
|
async function resolveCliTarget(positionals, decisions) {
|
|
148
211
|
loadCwdEnv();
|
|
149
212
|
const cwd = process.cwd();
|
|
150
213
|
const { input, intent } = parseTargetArgs(positionals, cwd);
|
|
151
214
|
const resolved = await resolveTarget({ input, intent, cwd, decisions });
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
215
|
+
const cleanup = resolved.cleanup;
|
|
216
|
+
try {
|
|
217
|
+
if (resolved.kind === "unavailable")
|
|
218
|
+
fail(resolved.reason);
|
|
219
|
+
if (resolved.kind === "empty")
|
|
220
|
+
fail(resolved.reason);
|
|
221
|
+
if (resolved.kind === "needs-build") {
|
|
222
|
+
fail(resolved.command ? `${resolved.reason}
|
|
223
|
+
→ ${resolved.command}` : resolved.reason, 2);
|
|
224
|
+
}
|
|
225
|
+
if (resolved.kind === "explicit") {
|
|
226
|
+
return {
|
|
227
|
+
entryFile: resolved.entryFile,
|
|
228
|
+
entryPointSource: resolved.entryPointSource,
|
|
229
|
+
cleanup
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (resolved.kind === "ok") {
|
|
233
|
+
return {
|
|
234
|
+
entryFile: resolved.entryFile,
|
|
235
|
+
entryPointSource: resolved.entryPointSource,
|
|
236
|
+
cleanup
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const chosen = await choosePackage(resolved.candidates, cwd);
|
|
240
|
+
const picked = pickEntry(chosen.dir);
|
|
241
|
+
if (!picked)
|
|
242
|
+
fail(`no TypeScript entry found in ${chosen.name}`);
|
|
243
|
+
return { ...picked, cleanup };
|
|
244
|
+
} catch (err) {
|
|
245
|
+
cleanup?.();
|
|
246
|
+
throw err;
|
|
169
247
|
}
|
|
170
|
-
const chosen = await choosePackage(resolved.candidates, cwd);
|
|
171
|
-
const picked = pickEntry(chosen.dir);
|
|
172
|
-
if (!picked)
|
|
173
|
-
fail(`no TypeScript entry found in ${chosen.name}`);
|
|
174
|
-
return picked;
|
|
175
248
|
}
|
|
176
249
|
function toList(value) {
|
|
177
250
|
if (!value)
|
|
@@ -223,24 +296,31 @@ async function specCommand(args) {
|
|
|
223
296
|
ignore: toList(values.ignore),
|
|
224
297
|
...values.jev ? { decisions: "jev" } : {}
|
|
225
298
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
299
|
+
let cleanup;
|
|
300
|
+
try {
|
|
301
|
+
const resolved = await resolveCliTarget(positionals, cliConfig.decisions ?? fileConfig?.decisions);
|
|
302
|
+
cleanup = resolved.cleanup;
|
|
303
|
+
const { entryFile, entryPointSource } = resolved;
|
|
304
|
+
const config = mergeConfig(fileConfig, cliConfig);
|
|
305
|
+
if (config.followExternal === "auto" && config.decisions !== "jev") {
|
|
306
|
+
fail("followExternal auto requires --jev");
|
|
307
|
+
}
|
|
308
|
+
const { spec, diagnostics } = await extractSpec({
|
|
309
|
+
entryFile,
|
|
310
|
+
entryPointSource,
|
|
311
|
+
followExternal: config.followExternal,
|
|
312
|
+
only: config.only,
|
|
313
|
+
ignore: config.ignore,
|
|
314
|
+
externals: config.externals,
|
|
315
|
+
decisions: config.decisions
|
|
316
|
+
});
|
|
317
|
+
reportDiagnostics(diagnostics);
|
|
318
|
+
if (!config.followExternal)
|
|
319
|
+
reportStubbedExternals(spec);
|
|
320
|
+
write(JSON.stringify(spec, null, 2), values.output);
|
|
321
|
+
} finally {
|
|
322
|
+
cleanup?.();
|
|
230
323
|
}
|
|
231
|
-
const { spec, diagnostics } = await extractSpec({
|
|
232
|
-
entryFile,
|
|
233
|
-
entryPointSource,
|
|
234
|
-
followExternal: config.followExternal,
|
|
235
|
-
only: config.only,
|
|
236
|
-
ignore: config.ignore,
|
|
237
|
-
externals: config.externals,
|
|
238
|
-
decisions: config.decisions
|
|
239
|
-
});
|
|
240
|
-
reportDiagnostics(diagnostics);
|
|
241
|
-
if (!config.followExternal)
|
|
242
|
-
reportStubbedExternals(spec);
|
|
243
|
-
write(JSON.stringify(spec, null, 2), values.output);
|
|
244
324
|
}
|
|
245
325
|
async function docsCommand(args) {
|
|
246
326
|
const { values, positionals } = parseArgs({
|
|
@@ -256,17 +336,26 @@ async function docsCommand(args) {
|
|
|
256
336
|
if (!["md", "html", "json"].includes(format))
|
|
257
337
|
fail(`unknown format "${format}" (md|html|json)`);
|
|
258
338
|
let docs;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
339
|
+
let cleanup;
|
|
340
|
+
try {
|
|
341
|
+
if (positionals[0]?.endsWith(".json")) {
|
|
342
|
+
docs = createDocs(positionals[0]);
|
|
343
|
+
} else {
|
|
344
|
+
const decisions = values.jev ? "jev" : loadConfig(process.cwd())?.decisions;
|
|
345
|
+
const resolved = await resolveCliTarget(positionals, decisions);
|
|
346
|
+
cleanup = resolved.cleanup;
|
|
347
|
+
const { spec, diagnostics } = await extractSpec({
|
|
348
|
+
entryFile: resolved.entryFile,
|
|
349
|
+
entryPointSource: resolved.entryPointSource
|
|
350
|
+
});
|
|
351
|
+
reportDiagnostics(diagnostics);
|
|
352
|
+
docs = createDocs(spec);
|
|
353
|
+
}
|
|
354
|
+
const content = format === "md" ? docs.toMarkdown() : format === "html" ? docs.toHTML() : JSON.stringify(docs.toJSON(), null, 2);
|
|
355
|
+
write(content, values.output);
|
|
356
|
+
} finally {
|
|
357
|
+
cleanup?.();
|
|
267
358
|
}
|
|
268
|
-
const content = format === "md" ? docs.toMarkdown() : format === "html" ? docs.toHTML() : JSON.stringify(docs.toJSON(), null, 2);
|
|
269
|
-
write(content, values.output);
|
|
270
359
|
}
|
|
271
360
|
async function listCommand(args) {
|
|
272
361
|
const { values, positionals } = parseArgs({
|
|
@@ -275,26 +364,32 @@ async function listCommand(args) {
|
|
|
275
364
|
allowPositionals: true
|
|
276
365
|
});
|
|
277
366
|
const decisions = values.jev ? "jev" : loadConfig(process.cwd())?.decisions;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
367
|
+
let cleanup;
|
|
368
|
+
try {
|
|
369
|
+
const resolved = await resolveCliTarget(positionals, decisions);
|
|
370
|
+
cleanup = resolved.cleanup;
|
|
371
|
+
const { exports, errors } = await listExports({ entryFile: resolved.entryFile });
|
|
372
|
+
for (const err of errors) {
|
|
373
|
+
console.error(`error: ${err}`);
|
|
374
|
+
}
|
|
375
|
+
if (errors.length > 0 && exports.length === 0) {
|
|
376
|
+
throw new CliError("", 1, true);
|
|
377
|
+
}
|
|
378
|
+
if (values.json) {
|
|
379
|
+
console.log(JSON.stringify(exports, null, 2));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
for (const exp of exports) {
|
|
383
|
+
const location = exp.file ? ` (${exp.file}:${exp.line})` : "";
|
|
384
|
+
console.log(`${exp.kind.padEnd(10)}${exp.name}${location}`);
|
|
385
|
+
}
|
|
386
|
+
} finally {
|
|
387
|
+
cleanup?.();
|
|
293
388
|
}
|
|
294
389
|
}
|
|
295
390
|
function readSpecFile(file) {
|
|
296
391
|
try {
|
|
297
|
-
return JSON.parse(
|
|
392
|
+
return JSON.parse(fs2.readFileSync(file, "utf-8"));
|
|
298
393
|
} catch (err) {
|
|
299
394
|
fail(`failed to read spec file ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
300
395
|
}
|
|
@@ -330,7 +425,7 @@ function validateCommand(args) {
|
|
|
330
425
|
for (const e of errors) {
|
|
331
426
|
console.error(`${e.instancePath || "/"} ${e.message}`);
|
|
332
427
|
}
|
|
333
|
-
|
|
428
|
+
throw new CliError("", 1, true);
|
|
334
429
|
}
|
|
335
430
|
function diffCommand(args) {
|
|
336
431
|
const { values, positionals } = parseArgs({
|
|
@@ -412,5 +507,11 @@ async function main() {
|
|
|
412
507
|
}
|
|
413
508
|
}
|
|
414
509
|
main().catch((err) => {
|
|
415
|
-
|
|
510
|
+
if (err instanceof CliError) {
|
|
511
|
+
if (!err.printed && err.message)
|
|
512
|
+
console.error(`error: ${err.message}`);
|
|
513
|
+
process.exit(err.exitCode);
|
|
514
|
+
}
|
|
515
|
+
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
516
|
+
process.exit(1);
|
|
416
517
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpkg-ts/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "CLI for OpenPkg - extract TypeScript API specs and generate docs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openpkg",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@openpkg-ts/sdk": "^0.52.
|
|
38
|
+
"@openpkg-ts/sdk": "^0.52.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/bun": "latest",
|