@openpkg-ts/cli 0.9.3 → 0.11.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/dist/index.js +134 -15
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,32 +5,49 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import {
|
|
8
|
+
calculateNextVersion,
|
|
9
|
+
categorizeBreakingChanges,
|
|
8
10
|
createDocs,
|
|
9
11
|
diffSpecs,
|
|
10
12
|
extractSpec,
|
|
13
|
+
getAvailableVersions,
|
|
14
|
+
getValidationErrors,
|
|
11
15
|
listExports,
|
|
16
|
+
loadConfig,
|
|
17
|
+
mergeConfig,
|
|
12
18
|
recommendSemverBump
|
|
13
19
|
} from "@openpkg-ts/sdk";
|
|
14
20
|
var HELP = `openpkg - extract TypeScript API specs and generate docs
|
|
15
21
|
|
|
16
22
|
Usage:
|
|
17
|
-
openpkg spec <entry.ts> [-o spec.json]
|
|
23
|
+
openpkg spec <entry.ts> [-o spec.json] [--follow-external <pkg,...>]
|
|
18
24
|
openpkg docs <entry.ts | spec.json> [-f md|html|json] [-o out]
|
|
19
25
|
openpkg list <entry.ts> [--json]
|
|
20
|
-
openpkg
|
|
26
|
+
openpkg validate <spec.json>
|
|
27
|
+
openpkg diff <old.json> <new.json> [--json]
|
|
21
28
|
|
|
22
29
|
Commands:
|
|
23
|
-
spec
|
|
24
|
-
docs
|
|
25
|
-
list
|
|
26
|
-
|
|
30
|
+
spec Extract an OpenPkg spec from a TypeScript entry point
|
|
31
|
+
docs Generate docs from an entry point or an existing spec file
|
|
32
|
+
list List exports (name, kind, location)
|
|
33
|
+
validate Validate a spec file against the OpenPkg meta-schema
|
|
34
|
+
diff Compare two spec files and recommend a semver bump
|
|
27
35
|
|
|
28
36
|
Options:
|
|
29
|
-
-o, --output
|
|
30
|
-
-f, --format
|
|
31
|
-
--json
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
-o, --output Write to file instead of stdout
|
|
38
|
+
-f, --format docs output format: md (default), html, json
|
|
39
|
+
--json list/diff output as JSON
|
|
40
|
+
--follow-external Expand types from these packages (comma-separated,
|
|
41
|
+
globs ok: "@ai-sdk/*"). Default: stub externals.
|
|
42
|
+
--follow-external-all Expand every external package (use with care)
|
|
43
|
+
--only Only extract these exports (comma-separated, * ok)
|
|
44
|
+
--ignore Ignore these exports (comma-separated, * ok)
|
|
45
|
+
-h, --help Show this help
|
|
46
|
+
-v, --version Show version
|
|
47
|
+
|
|
48
|
+
Config: reads openpkg.config.json (or package.json "openpkg" field) from the
|
|
49
|
+
cwd. Flags override the file. Example openpkg.config.json:
|
|
50
|
+
{ "followExternal": ["@acme/payment-kit", "@ai-sdk/*"] }
|
|
34
51
|
`;
|
|
35
52
|
function fail(message) {
|
|
36
53
|
console.error(`error: ${message}`);
|
|
@@ -59,17 +76,59 @@ function reportDiagnostics(diagnostics) {
|
|
|
59
76
|
process.exit(1);
|
|
60
77
|
}
|
|
61
78
|
}
|
|
79
|
+
function toList(value) {
|
|
80
|
+
if (!value)
|
|
81
|
+
return;
|
|
82
|
+
const items = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
83
|
+
return items.length > 0 ? items : undefined;
|
|
84
|
+
}
|
|
85
|
+
function reportStubbedExternals(spec) {
|
|
86
|
+
const counts = new Map;
|
|
87
|
+
for (const t of spec.types ?? []) {
|
|
88
|
+
if (!t.external)
|
|
89
|
+
continue;
|
|
90
|
+
const pkg = t.schema?.["x-ts-package"];
|
|
91
|
+
const key = typeof pkg === "string" ? pkg : "(unknown origin)";
|
|
92
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
93
|
+
}
|
|
94
|
+
if (counts.size === 0)
|
|
95
|
+
return;
|
|
96
|
+
const summary = [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([pkg, n]) => `${pkg} (${n})`).join(", ");
|
|
97
|
+
console.error(`external types stubbed from: ${summary}`);
|
|
98
|
+
console.error(" → add package names to followExternal (config or --follow-external) to expand them");
|
|
99
|
+
}
|
|
62
100
|
async function specCommand(args) {
|
|
63
101
|
const { values, positionals } = parseArgs({
|
|
64
102
|
args,
|
|
65
|
-
options: {
|
|
103
|
+
options: {
|
|
104
|
+
output: { type: "string", short: "o" },
|
|
105
|
+
"follow-external": { type: "string" },
|
|
106
|
+
"follow-external-all": { type: "boolean" },
|
|
107
|
+
only: { type: "string" },
|
|
108
|
+
ignore: { type: "string" }
|
|
109
|
+
},
|
|
66
110
|
allowPositionals: true
|
|
67
111
|
});
|
|
68
112
|
const entryFile = positionals[0];
|
|
69
113
|
if (!entryFile)
|
|
70
114
|
fail("spec requires an entry file (openpkg spec src/index.ts)");
|
|
71
|
-
const
|
|
115
|
+
const fileConfig = loadConfig(process.cwd());
|
|
116
|
+
const cliConfig = {
|
|
117
|
+
followExternal: values["follow-external-all"] ? true : toList(values["follow-external"]),
|
|
118
|
+
only: toList(values.only),
|
|
119
|
+
ignore: toList(values.ignore)
|
|
120
|
+
};
|
|
121
|
+
const config = mergeConfig(fileConfig, cliConfig);
|
|
122
|
+
const { spec, diagnostics } = await extractSpec({
|
|
123
|
+
entryFile,
|
|
124
|
+
followExternal: config.followExternal,
|
|
125
|
+
only: config.only,
|
|
126
|
+
ignore: config.ignore,
|
|
127
|
+
externals: config.externals
|
|
128
|
+
});
|
|
72
129
|
reportDiagnostics(diagnostics);
|
|
130
|
+
if (!config.followExternal)
|
|
131
|
+
reportStubbedExternals(spec);
|
|
73
132
|
write(JSON.stringify(spec, null, 2), values.output);
|
|
74
133
|
}
|
|
75
134
|
async function docsCommand(args) {
|
|
@@ -130,12 +189,67 @@ function readSpecFile(file) {
|
|
|
130
189
|
fail(`failed to read spec file ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
131
190
|
}
|
|
132
191
|
}
|
|
192
|
+
function pickVersion(spec) {
|
|
193
|
+
const declared = spec?.openpkg;
|
|
194
|
+
if (typeof declared === "string" && getAvailableVersions().includes(declared)) {
|
|
195
|
+
return declared;
|
|
196
|
+
}
|
|
197
|
+
return "latest";
|
|
198
|
+
}
|
|
199
|
+
function readValidSpecFile(file) {
|
|
200
|
+
const parsed = readSpecFile(file);
|
|
201
|
+
const errors = getValidationErrors(parsed, pickVersion(parsed));
|
|
202
|
+
if (errors.length > 0) {
|
|
203
|
+
const details = errors.map((e) => ` ${e.instancePath || "/"} ${e.message}`).join(`
|
|
204
|
+
`);
|
|
205
|
+
fail(`invalid spec ${file}:
|
|
206
|
+
${details}`);
|
|
207
|
+
}
|
|
208
|
+
return parsed;
|
|
209
|
+
}
|
|
210
|
+
function validateCommand(args) {
|
|
211
|
+
const [file] = args;
|
|
212
|
+
if (!file)
|
|
213
|
+
fail("validate requires a spec file (openpkg validate spec.json)");
|
|
214
|
+
const spec = readSpecFile(file);
|
|
215
|
+
const errors = getValidationErrors(spec, pickVersion(spec));
|
|
216
|
+
if (errors.length === 0) {
|
|
217
|
+
console.log(`${file}: valid`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
for (const e of errors) {
|
|
221
|
+
console.error(`${e.instancePath || "/"} ${e.message}`);
|
|
222
|
+
}
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
133
225
|
function diffCommand(args) {
|
|
134
|
-
const
|
|
226
|
+
const { values, positionals } = parseArgs({
|
|
227
|
+
args,
|
|
228
|
+
options: { json: { type: "boolean" } },
|
|
229
|
+
allowPositionals: true
|
|
230
|
+
});
|
|
231
|
+
const [oldFile, newFile] = positionals;
|
|
135
232
|
if (!oldFile || !newFile)
|
|
136
233
|
fail("diff requires two spec files (openpkg diff old.json new.json)");
|
|
137
|
-
const
|
|
234
|
+
const oldSpec = readValidSpecFile(oldFile);
|
|
235
|
+
const newSpec = readValidSpecFile(newFile);
|
|
236
|
+
const diff = diffSpecs(oldSpec, newSpec);
|
|
138
237
|
const recommendation = recommendSemverBump(diff);
|
|
238
|
+
const oldVersion = newSpec?.meta?.version;
|
|
239
|
+
const nextVersion = oldVersion ? calculateNextVersion(oldVersion, recommendation.bump) : undefined;
|
|
240
|
+
if (values.json) {
|
|
241
|
+
console.log(JSON.stringify({
|
|
242
|
+
breaking: diff.breaking,
|
|
243
|
+
nonBreaking: diff.nonBreaking,
|
|
244
|
+
docsOnly: diff.docsOnly,
|
|
245
|
+
categorized: categorizeBreakingChanges(diff.breaking, oldSpec, newSpec),
|
|
246
|
+
recommendation,
|
|
247
|
+
...nextVersion ? { nextVersion } : {}
|
|
248
|
+
}, null, 2));
|
|
249
|
+
if (diff.breaking.length)
|
|
250
|
+
process.exitCode = 2;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
139
253
|
const section = (title, items) => {
|
|
140
254
|
if (!items.length)
|
|
141
255
|
return;
|
|
@@ -151,6 +265,8 @@ function diffCommand(args) {
|
|
|
151
265
|
}
|
|
152
266
|
console.log(`
|
|
153
267
|
Recommended bump: ${recommendation.bump} (${recommendation.reason})`);
|
|
268
|
+
if (nextVersion)
|
|
269
|
+
console.log(`Next version: ${nextVersion}`);
|
|
154
270
|
if (diff.breaking.length)
|
|
155
271
|
process.exitCode = 2;
|
|
156
272
|
}
|
|
@@ -166,6 +282,9 @@ async function main() {
|
|
|
166
282
|
case "list":
|
|
167
283
|
await listCommand(rest);
|
|
168
284
|
break;
|
|
285
|
+
case "validate":
|
|
286
|
+
validateCommand(rest);
|
|
287
|
+
break;
|
|
169
288
|
case "diff":
|
|
170
289
|
diffCommand(rest);
|
|
171
290
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpkg-ts/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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.
|
|
38
|
+
"@openpkg-ts/sdk": "^0.48.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/bun": "latest",
|