@openpkg-ts/cli 0.2.3 → 0.3.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.
@@ -0,0 +1,701 @@
1
+ #!/usr/bin/env bun
2
+ import {
3
+ __require,
4
+ __toESM
5
+ } from "../shared/chunk-1dqs11h6.js";
6
+
7
+ // bin/openpkg.ts
8
+ import * as path10 from "node:path";
9
+ import { getExport, listExports } from "@openpkg-ts/sdk";
10
+ import { Command as Command10 } from "commander";
11
+ // package.json
12
+ var package_default = {
13
+ name: "@openpkg-ts/cli",
14
+ version: "0.3.0",
15
+ description: "CLI for OpenPkg TypeScript API extraction and documentation generation",
16
+ homepage: "https://github.com/ryanwaits/openpkg-ts#readme",
17
+ repository: {
18
+ type: "git",
19
+ url: "git+https://github.com/ryanwaits/openpkg-ts.git",
20
+ directory: "packages/cli"
21
+ },
22
+ type: "module",
23
+ main: "./dist/src/index.js",
24
+ types: "./dist/src/index.d.ts",
25
+ bin: {
26
+ openpkg: "./dist/bin/openpkg.js"
27
+ },
28
+ files: [
29
+ "dist"
30
+ ],
31
+ scripts: {
32
+ build: "bunup",
33
+ dev: "bunup --watch",
34
+ test: "bun test"
35
+ },
36
+ dependencies: {
37
+ "@openpkg-ts/adapters": "^0.3.0",
38
+ "@openpkg-ts/sdk": "^0.31.0",
39
+ commander: "^14.0.0"
40
+ },
41
+ devDependencies: {
42
+ "@types/bun": "latest",
43
+ "@types/node": "^20.0.0",
44
+ bunup: "latest"
45
+ },
46
+ publishConfig: {
47
+ access: "public"
48
+ }
49
+ };
50
+
51
+ // src/commands/breaking.ts
52
+ import * as fs from "node:fs";
53
+ import * as path from "node:path";
54
+ import {
55
+ categorizeBreakingChanges,
56
+ diffSpec
57
+ } from "@openpkg-ts/spec";
58
+ import { Command } from "commander";
59
+ function loadSpec(filePath) {
60
+ const resolved = path.resolve(filePath);
61
+ const content = fs.readFileSync(resolved, "utf-8");
62
+ return JSON.parse(content);
63
+ }
64
+ function createBreakingCommand() {
65
+ return new Command("breaking").description("Check for breaking changes between two specs").argument("<old>", "Path to old spec file (JSON)").argument("<new>", "Path to new spec file (JSON)").action(async (oldPath, newPath) => {
66
+ try {
67
+ const oldSpec = loadSpec(oldPath);
68
+ const newSpec = loadSpec(newPath);
69
+ const diff = diffSpec(oldSpec, newSpec);
70
+ const categorized = categorizeBreakingChanges(diff.breaking, oldSpec, newSpec);
71
+ const result = {
72
+ breaking: categorized,
73
+ count: categorized.length
74
+ };
75
+ console.log(JSON.stringify(result, null, 2));
76
+ if (categorized.length > 0) {
77
+ process.exit(1);
78
+ }
79
+ } catch (err) {
80
+ const error = err instanceof Error ? err : new Error(String(err));
81
+ console.error(JSON.stringify({ error: error.message }, null, 2));
82
+ process.exit(1);
83
+ }
84
+ });
85
+ }
86
+
87
+ // src/commands/changelog.ts
88
+ import * as fs3 from "node:fs";
89
+ import * as path3 from "node:path";
90
+ import { Command as Command3 } from "commander";
91
+
92
+ // src/commands/diff.ts
93
+ import * as fs2 from "node:fs";
94
+ import * as path2 from "node:path";
95
+ import {
96
+ categorizeBreakingChanges as categorizeBreakingChanges2,
97
+ diffSpec as diffSpec2,
98
+ recommendSemverBump
99
+ } from "@openpkg-ts/spec";
100
+ import { Command as Command2 } from "commander";
101
+ function loadSpec2(filePath) {
102
+ const resolved = path2.resolve(filePath);
103
+ const content = fs2.readFileSync(resolved, "utf-8");
104
+ return JSON.parse(content);
105
+ }
106
+ function toExportMap(spec) {
107
+ const map = new Map;
108
+ for (const exp of spec.exports) {
109
+ map.set(exp.id, { name: exp.name, kind: exp.kind });
110
+ }
111
+ if (spec.types) {
112
+ for (const t of spec.types) {
113
+ map.set(t.id, { name: t.name, kind: t.kind });
114
+ }
115
+ }
116
+ return map;
117
+ }
118
+ function enrichDiff(oldSpec, newSpec) {
119
+ const rawDiff = diffSpec2(oldSpec, newSpec);
120
+ const categorized = categorizeBreakingChanges2(rawDiff.breaking, oldSpec, newSpec);
121
+ const semver = recommendSemverBump(rawDiff);
122
+ const oldExports = toExportMap(oldSpec);
123
+ const removed = [];
124
+ const changed = [];
125
+ const breaking = [];
126
+ for (const cat of categorized) {
127
+ if (cat.reason === "removed") {
128
+ const info = oldExports.get(cat.id);
129
+ removed.push({
130
+ id: cat.id,
131
+ name: cat.name,
132
+ kind: info?.kind ?? cat.kind
133
+ });
134
+ } else {
135
+ changed.push({
136
+ id: cat.id,
137
+ name: cat.name,
138
+ kind: cat.kind,
139
+ description: describeChange(cat)
140
+ });
141
+ breaking.push(cat);
142
+ }
143
+ }
144
+ const added = rawDiff.nonBreaking;
145
+ return {
146
+ breaking,
147
+ added,
148
+ removed,
149
+ changed,
150
+ docsOnly: rawDiff.docsOnly,
151
+ summary: {
152
+ breakingCount: breaking.length,
153
+ addedCount: added.length,
154
+ removedCount: removed.length,
155
+ changedCount: changed.length,
156
+ docsOnlyCount: rawDiff.docsOnly.length,
157
+ semverBump: semver.bump,
158
+ semverReason: semver.reason
159
+ }
160
+ };
161
+ }
162
+ function describeChange(cat) {
163
+ switch (cat.reason) {
164
+ case "signature changed":
165
+ return `Function signature changed`;
166
+ case "type definition changed":
167
+ return `Type definition changed`;
168
+ case "constructor changed":
169
+ return `Class constructor signature changed`;
170
+ case "methods removed":
171
+ return `Class methods removed`;
172
+ case "methods changed":
173
+ return `Class methods changed`;
174
+ case "changed":
175
+ return `${cat.kind} changed`;
176
+ default:
177
+ return cat.reason;
178
+ }
179
+ }
180
+ function createDiffCommand() {
181
+ return new Command2("diff").description("Compare two OpenPkg specs and show differences").argument("<old>", "Path to old spec file (JSON)").argument("<new>", "Path to new spec file (JSON)").option("--json", "Output as JSON (default)").option("--summary", "Only show summary").action(async (oldPath, newPath, options) => {
182
+ try {
183
+ const oldSpec = loadSpec2(oldPath);
184
+ const newSpec = loadSpec2(newPath);
185
+ const result = enrichDiff(oldSpec, newSpec);
186
+ if (options.summary) {
187
+ console.log(JSON.stringify(result.summary, null, 2));
188
+ } else {
189
+ console.log(JSON.stringify(result, null, 2));
190
+ }
191
+ } catch (err) {
192
+ const error = err instanceof Error ? err : new Error(String(err));
193
+ console.error(JSON.stringify({ error: error.message }, null, 2));
194
+ process.exit(1);
195
+ }
196
+ });
197
+ }
198
+
199
+ // src/commands/changelog.ts
200
+ function loadSpec3(filePath) {
201
+ const resolved = path3.resolve(filePath);
202
+ const content = fs3.readFileSync(resolved, "utf-8");
203
+ return JSON.parse(content);
204
+ }
205
+ function formatMarkdown(diff) {
206
+ const lines = [];
207
+ if (diff.removed.length > 0 || diff.changed.length > 0) {
208
+ lines.push("## Breaking Changes");
209
+ lines.push("");
210
+ for (const r of diff.removed) {
211
+ lines.push(`- **Removed** \`${r.name}\` (${r.kind})`);
212
+ }
213
+ for (const c of diff.changed) {
214
+ lines.push(`- **${c.name}** (${c.kind}): ${c.description}`);
215
+ }
216
+ lines.push("");
217
+ }
218
+ if (diff.added.length > 0) {
219
+ lines.push("## Added");
220
+ lines.push("");
221
+ for (const id of diff.added) {
222
+ lines.push(`- \`${id}\``);
223
+ }
224
+ lines.push("");
225
+ }
226
+ if (diff.docsOnly.length > 0) {
227
+ lines.push("## Changed");
228
+ lines.push("");
229
+ for (const id of diff.docsOnly) {
230
+ lines.push(`- \`${id}\` (docs)`);
231
+ }
232
+ lines.push("");
233
+ }
234
+ return lines.join(`
235
+ `).trim() || "No changes detected.";
236
+ }
237
+ function createChangelogCommand() {
238
+ return new Command3("changelog").description("Generate changelog from diff between two specs").argument("<old>", "Path to old spec file (JSON)").argument("<new>", "Path to new spec file (JSON)").option("--format <format>", "Output format: md or json", "md").action(async (oldPath, newPath, options) => {
239
+ try {
240
+ const oldSpec = loadSpec3(oldPath);
241
+ const newSpec = loadSpec3(newPath);
242
+ const diff = enrichDiff(oldSpec, newSpec);
243
+ if (options.format === "json") {
244
+ console.log(JSON.stringify(diff, null, 2));
245
+ } else {
246
+ console.log(formatMarkdown(diff));
247
+ }
248
+ } catch (err) {
249
+ const error = err instanceof Error ? err : new Error(String(err));
250
+ console.error(JSON.stringify({ error: error.message }, null, 2));
251
+ process.exit(1);
252
+ }
253
+ });
254
+ }
255
+
256
+ // src/commands/diagnostics.ts
257
+ import * as fs4 from "node:fs";
258
+ import * as path4 from "node:path";
259
+ import { analyzeSpec } from "@openpkg-ts/sdk";
260
+ import { Command as Command4 } from "commander";
261
+ function loadJSON(filePath) {
262
+ const resolved = path4.resolve(filePath);
263
+ const content = fs4.readFileSync(resolved, "utf-8");
264
+ return JSON.parse(content);
265
+ }
266
+ function createDiagnosticsCommand() {
267
+ return new Command4("diagnostics").description("Analyze spec for quality issues (missing docs, deprecated without reason)").argument("<spec>", "Path to spec file (JSON)").action(async (specPath) => {
268
+ try {
269
+ const spec = loadJSON(specPath);
270
+ const diagnostics = analyzeSpec(spec);
271
+ const result = {
272
+ summary: {
273
+ total: diagnostics.missingDescriptions.length + diagnostics.deprecatedNoReason.length + diagnostics.missingParamDocs.length,
274
+ missingDescriptions: diagnostics.missingDescriptions.length,
275
+ deprecatedNoReason: diagnostics.deprecatedNoReason.length,
276
+ missingParamDocs: diagnostics.missingParamDocs.length
277
+ },
278
+ diagnostics
279
+ };
280
+ console.log(JSON.stringify(result, null, 2));
281
+ process.exit(0);
282
+ } catch (err) {
283
+ const error = err instanceof Error ? err : new Error(String(err));
284
+ console.log(JSON.stringify({ error: error.message }, null, 2));
285
+ process.exit(0);
286
+ }
287
+ });
288
+ }
289
+
290
+ // src/commands/docs.ts
291
+ import * as fs5 from "node:fs";
292
+ import * as path5 from "node:path";
293
+ import { createDocs, loadSpec as loadSpec4 } from "@openpkg-ts/sdk";
294
+ import { Command as Command5 } from "commander";
295
+ async function readStdin() {
296
+ const chunks = [];
297
+ for await (const chunk of process.stdin) {
298
+ chunks.push(chunk);
299
+ }
300
+ return Buffer.concat(chunks).toString("utf-8");
301
+ }
302
+ function getExtension(format) {
303
+ switch (format) {
304
+ case "json":
305
+ return ".json";
306
+ case "html":
307
+ return ".html";
308
+ default:
309
+ return ".md";
310
+ }
311
+ }
312
+ function renderExport(docs, exportId, format) {
313
+ const exp = docs.getExport(exportId);
314
+ if (!exp)
315
+ throw new Error(`Export not found: ${exportId}`);
316
+ switch (format) {
317
+ case "json":
318
+ return JSON.stringify(docs.toJSON({ export: exportId }), null, 2);
319
+ case "html":
320
+ return docs.toHTML({ export: exportId });
321
+ default:
322
+ return docs.toMarkdown({ export: exportId, frontmatter: true, codeSignatures: true });
323
+ }
324
+ }
325
+ function renderFull(docs, format) {
326
+ switch (format) {
327
+ case "json":
328
+ return JSON.stringify(docs.toJSON(), null, 2);
329
+ case "html":
330
+ return docs.toHTML();
331
+ default:
332
+ return docs.toMarkdown({ frontmatter: true, codeSignatures: true });
333
+ }
334
+ }
335
+ function createDocsCommand() {
336
+ return new Command5("docs").description("Generate documentation from OpenPkg spec").argument("<spec>", "Path to openpkg.json spec file (use - for stdin)").option("-o, --output <path>", "Output file or directory (default: stdout)").option("-f, --format <format>", "Output format: md, json, html (default: md)", "md").option("--split", "Output one file per export (requires --output as directory)").option("-e, --export <name>", "Generate docs for a single export by name").option("-a, --adapter <name>", "Use adapter for generation (default: raw)").action(async (specPath, options) => {
337
+ const format = options.format || "md";
338
+ try {
339
+ if (options.adapter && options.adapter !== "raw") {
340
+ let getAdapter;
341
+ try {
342
+ const _adapterModule = await import(`@openpkg-ts/adapters/${options.adapter}`);
343
+ const registryModule = await import("@openpkg-ts/adapters");
344
+ getAdapter = registryModule.getAdapter;
345
+ } catch {
346
+ console.error(JSON.stringify({ error: `Failed to load adapter: ${options.adapter}` }));
347
+ process.exit(1);
348
+ }
349
+ const adapter = getAdapter(options.adapter);
350
+ if (!adapter) {
351
+ console.error(JSON.stringify({ error: `Unknown adapter: ${options.adapter}` }));
352
+ process.exit(1);
353
+ }
354
+ if (!options.output) {
355
+ console.error(JSON.stringify({ error: "--adapter requires --output <directory>" }));
356
+ process.exit(1);
357
+ }
358
+ let spec;
359
+ if (specPath === "-") {
360
+ const input = await readStdin();
361
+ spec = JSON.parse(input);
362
+ } else {
363
+ const specFile = path5.resolve(specPath);
364
+ if (!fs5.existsSync(specFile)) {
365
+ console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
366
+ process.exit(1);
367
+ }
368
+ spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
369
+ }
370
+ await adapter.generate(spec, path5.resolve(options.output));
371
+ console.error(`Generated docs with ${options.adapter} adapter to ${options.output}`);
372
+ return;
373
+ }
374
+ let docs;
375
+ if (specPath === "-") {
376
+ const input = await readStdin();
377
+ const spec = JSON.parse(input);
378
+ docs = loadSpec4(spec);
379
+ } else {
380
+ const specFile = path5.resolve(specPath);
381
+ if (!fs5.existsSync(specFile)) {
382
+ console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
383
+ process.exit(1);
384
+ }
385
+ docs = createDocs(specFile);
386
+ }
387
+ if (options.export) {
388
+ const exports = docs.getAllExports();
389
+ const exp = exports.find((e) => e.name === options.export);
390
+ if (!exp) {
391
+ console.error(JSON.stringify({ error: `Export not found: ${options.export}` }));
392
+ process.exit(1);
393
+ }
394
+ const output2 = renderExport(docs, exp.id, format);
395
+ if (options.output && options.output !== "-") {
396
+ const outputPath = path5.resolve(options.output);
397
+ fs5.writeFileSync(outputPath, output2);
398
+ console.error(`Wrote ${outputPath}`);
399
+ } else {
400
+ console.log(output2);
401
+ }
402
+ return;
403
+ }
404
+ if (options.split) {
405
+ if (!options.output) {
406
+ console.error(JSON.stringify({ error: "--split requires --output <directory>" }));
407
+ process.exit(1);
408
+ }
409
+ const outDir = path5.resolve(options.output);
410
+ if (!fs5.existsSync(outDir)) {
411
+ fs5.mkdirSync(outDir, { recursive: true });
412
+ }
413
+ const exports = docs.getAllExports();
414
+ for (const exp of exports) {
415
+ const filename = `${exp.name}${getExtension(format)}`;
416
+ const filePath = path5.join(outDir, filename);
417
+ const content = renderExport(docs, exp.id, format);
418
+ fs5.writeFileSync(filePath, content);
419
+ }
420
+ console.error(`Wrote ${exports.length} files to ${outDir}`);
421
+ return;
422
+ }
423
+ const output = renderFull(docs, format);
424
+ if (options.output && options.output !== "-") {
425
+ const outputPath = path5.resolve(options.output);
426
+ fs5.writeFileSync(outputPath, output);
427
+ console.error(`Wrote ${outputPath}`);
428
+ } else {
429
+ console.log(output);
430
+ }
431
+ } catch (err) {
432
+ const error = err instanceof Error ? err : new Error(String(err));
433
+ console.error(JSON.stringify({ error: error.message }));
434
+ process.exit(1);
435
+ }
436
+ });
437
+ }
438
+
439
+ // src/commands/filter.ts
440
+ import * as fs6 from "node:fs";
441
+ import * as path6 from "node:path";
442
+ import { filterSpec } from "@openpkg-ts/sdk";
443
+ import { Command as Command6 } from "commander";
444
+ var VALID_KINDS = [
445
+ "function",
446
+ "class",
447
+ "variable",
448
+ "interface",
449
+ "type",
450
+ "enum",
451
+ "module",
452
+ "namespace",
453
+ "reference",
454
+ "external"
455
+ ];
456
+ function loadSpec5(filePath) {
457
+ const resolved = path6.resolve(filePath);
458
+ const content = fs6.readFileSync(resolved, "utf-8");
459
+ return JSON.parse(content);
460
+ }
461
+ function parseList(val) {
462
+ if (!val)
463
+ return;
464
+ return val.split(",").map((s) => s.trim()).filter(Boolean);
465
+ }
466
+ function validateKinds(kinds) {
467
+ const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
468
+ if (invalid.length > 0) {
469
+ throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS.join(", ")}`);
470
+ }
471
+ return kinds;
472
+ }
473
+ function createFilterCommand() {
474
+ return new Command6("filter").description("Filter an OpenPkg spec by various criteria").argument("<spec>", "Path to spec file (JSON)").option("--kind <kinds>", "Filter by kinds (comma-separated)").option("--name <names>", "Filter by exact names (comma-separated)").option("--id <ids>", "Filter by IDs (comma-separated)").option("--tag <tags>", "Filter by tags (comma-separated)").option("--deprecated", "Only deprecated exports").option("--no-deprecated", "Exclude deprecated exports").option("--has-description", "Only exports with descriptions").option("--missing-description", "Only exports without descriptions").option("--search <term>", "Search name/description (case-insensitive)").option("--module <path>", "Filter by source file path (contains)").option("-o, --output <file>", "Output file (default: stdout)").option("--summary", "Only output matched/total counts").option("--quiet", "Output raw spec only (no wrapper)").action(async (specPath, options) => {
475
+ try {
476
+ const spec = loadSpec5(specPath);
477
+ const criteria = {};
478
+ if (options.kind) {
479
+ const kinds = parseList(options.kind);
480
+ if (kinds)
481
+ criteria.kinds = validateKinds(kinds);
482
+ }
483
+ if (options.name)
484
+ criteria.names = parseList(options.name);
485
+ if (options.id)
486
+ criteria.ids = parseList(options.id);
487
+ if (options.tag)
488
+ criteria.tags = parseList(options.tag);
489
+ if (options.deprecated !== undefined)
490
+ criteria.deprecated = options.deprecated;
491
+ if (options.hasDescription)
492
+ criteria.hasDescription = true;
493
+ if (options.missingDescription)
494
+ criteria.hasDescription = false;
495
+ if (options.search)
496
+ criteria.search = options.search;
497
+ if (options.module)
498
+ criteria.module = options.module;
499
+ const result = filterSpec(spec, criteria);
500
+ let output;
501
+ if (options.summary) {
502
+ output = { matched: result.matched, total: result.total };
503
+ } else if (options.quiet) {
504
+ output = result.spec;
505
+ } else {
506
+ output = { spec: result.spec, matched: result.matched, total: result.total };
507
+ }
508
+ const json = JSON.stringify(output, null, 2);
509
+ if (options.output) {
510
+ fs6.writeFileSync(path6.resolve(options.output), json);
511
+ } else {
512
+ console.log(json);
513
+ }
514
+ } catch (err) {
515
+ const error = err instanceof Error ? err : new Error(String(err));
516
+ console.error(JSON.stringify({ error: error.message }, null, 2));
517
+ process.exit(1);
518
+ }
519
+ });
520
+ }
521
+
522
+ // src/commands/semver.ts
523
+ import * as fs7 from "node:fs";
524
+ import * as path7 from "node:path";
525
+ import { diffSpec as diffSpec3, recommendSemverBump as recommendSemverBump2 } from "@openpkg-ts/spec";
526
+ import { Command as Command7 } from "commander";
527
+ function loadSpec6(filePath) {
528
+ const resolved = path7.resolve(filePath);
529
+ const content = fs7.readFileSync(resolved, "utf-8");
530
+ return JSON.parse(content);
531
+ }
532
+ function createSemverCommand() {
533
+ return new Command7("semver").description("Recommend semver bump based on spec changes").argument("<old>", "Path to old spec file (JSON)").argument("<new>", "Path to new spec file (JSON)").action(async (oldPath, newPath) => {
534
+ try {
535
+ const oldSpec = loadSpec6(oldPath);
536
+ const newSpec = loadSpec6(newPath);
537
+ const diff = diffSpec3(oldSpec, newSpec);
538
+ const recommendation = recommendSemverBump2(diff);
539
+ const result = {
540
+ bump: recommendation.bump,
541
+ reason: recommendation.reason
542
+ };
543
+ console.log(JSON.stringify(result, null, 2));
544
+ } catch (err) {
545
+ const error = err instanceof Error ? err : new Error(String(err));
546
+ console.error(JSON.stringify({ error: error.message }, null, 2));
547
+ process.exit(1);
548
+ }
549
+ });
550
+ }
551
+
552
+ // src/commands/snapshot.ts
553
+ import * as fs8 from "node:fs";
554
+ import * as path8 from "node:path";
555
+ import { extractSpec } from "@openpkg-ts/sdk";
556
+ import { Command as Command8 } from "commander";
557
+ function parseFilter(value) {
558
+ if (!value)
559
+ return;
560
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
561
+ }
562
+ function formatDiagnostics(diagnostics) {
563
+ return diagnostics.map((d) => ({
564
+ message: d.message,
565
+ severity: d.severity,
566
+ ...d.code && { code: d.code },
567
+ ...d.suggestion && { suggestion: d.suggestion },
568
+ ...d.location && { location: d.location }
569
+ }));
570
+ }
571
+ function createSnapshotCommand() {
572
+ return new Command8("snapshot").description("Generate full OpenPkg spec from TypeScript entry point").argument("<entry>", "Entry point file path").option("-o, --output <file>", "Output file (default: openpkg.json, use - for stdout)", "openpkg.json").option("--max-depth <n>", "Max type depth (default: 4)", "4").option("--skip-resolve", "Skip external type resolution").option("--runtime", "Enable Standard Schema runtime extraction").option("--only <exports>", "Filter exports (comma-separated, wildcards supported)").option("--ignore <exports>", "Ignore exports (comma-separated, wildcards supported)").option("--verify", "Exit 1 if any exports fail").action(async (entry, options) => {
573
+ const entryFile = path8.resolve(entry);
574
+ const extractOptions = {
575
+ entryFile,
576
+ maxTypeDepth: parseInt(options.maxDepth ?? "4", 10),
577
+ resolveExternalTypes: !options.skipResolve,
578
+ schemaExtraction: options.runtime ? "hybrid" : "static",
579
+ only: parseFilter(options.only),
580
+ ignore: parseFilter(options.ignore)
581
+ };
582
+ try {
583
+ const result = await extractSpec(extractOptions);
584
+ const summary = {
585
+ exports: result.spec.exports.length,
586
+ types: result.spec.types?.length ?? 0,
587
+ diagnostics: result.diagnostics.length,
588
+ ...result.verification && {
589
+ verification: {
590
+ discovered: result.verification.discovered,
591
+ extracted: result.verification.extracted,
592
+ skipped: result.verification.skipped,
593
+ failed: result.verification.failed
594
+ }
595
+ },
596
+ ...result.runtimeSchemas && {
597
+ runtime: {
598
+ extracted: result.runtimeSchemas.extracted,
599
+ merged: result.runtimeSchemas.merged,
600
+ vendors: result.runtimeSchemas.vendors
601
+ }
602
+ }
603
+ };
604
+ console.error(JSON.stringify(summary, null, 2));
605
+ if (options.verify && result.verification && result.verification.failed > 0) {
606
+ const errorOutput = {
607
+ error: "Export verification failed",
608
+ failed: result.verification.details.failed,
609
+ diagnostics: formatDiagnostics(result.diagnostics)
610
+ };
611
+ console.error(JSON.stringify(errorOutput, null, 2));
612
+ process.exit(1);
613
+ }
614
+ const specJson = JSON.stringify(result.spec, null, 2);
615
+ if (options.output === "-") {
616
+ console.log(specJson);
617
+ } else {
618
+ const outputPath = path8.resolve(options.output ?? "openpkg.json");
619
+ fs8.writeFileSync(outputPath, specJson);
620
+ console.error(`Wrote ${outputPath}`);
621
+ }
622
+ } catch (err) {
623
+ const error = err instanceof Error ? err : new Error(String(err));
624
+ const errorOutput = {
625
+ error: error.message,
626
+ ...error.stack && { stack: error.stack }
627
+ };
628
+ console.error(JSON.stringify(errorOutput, null, 2));
629
+ process.exit(1);
630
+ }
631
+ });
632
+ }
633
+
634
+ // src/commands/validate.ts
635
+ import * as fs9 from "node:fs";
636
+ import * as path9 from "node:path";
637
+ import { getValidationErrors } from "@openpkg-ts/spec";
638
+ import { Command as Command9 } from "commander";
639
+ function loadJSON2(filePath) {
640
+ const resolved = path9.resolve(filePath);
641
+ const content = fs9.readFileSync(resolved, "utf-8");
642
+ return JSON.parse(content);
643
+ }
644
+ function createValidateCommand() {
645
+ return new Command9("validate").description("Validate an OpenPkg spec against the schema").argument("<spec>", "Path to spec file (JSON)").option("--version <version>", "Schema version to validate against (default: latest)").action(async (specPath, options) => {
646
+ try {
647
+ const spec = loadJSON2(specPath);
648
+ const version = options.version ?? "latest";
649
+ const errors = getValidationErrors(spec, version);
650
+ const result = {
651
+ valid: errors.length === 0,
652
+ errors
653
+ };
654
+ console.log(JSON.stringify(result, null, 2));
655
+ if (errors.length > 0) {
656
+ process.exit(1);
657
+ }
658
+ } catch (err) {
659
+ const error = err instanceof Error ? err : new Error(String(err));
660
+ console.error(JSON.stringify({ error: error.message }, null, 2));
661
+ process.exit(1);
662
+ }
663
+ });
664
+ }
665
+
666
+ // bin/openpkg.ts
667
+ var program = new Command10;
668
+ program.name("openpkg").description("OpenPkg CLI - TypeScript API extraction primitives").version(package_default.version);
669
+ program.command("list").description("List exports from a TypeScript entry point").argument("<entry>", "Entry point file path").action(async (entry) => {
670
+ const entryFile = path10.resolve(entry);
671
+ const result = await listExports({ entryFile });
672
+ if (result.errors.length > 0) {
673
+ console.error(JSON.stringify({ errors: result.errors }, null, 2));
674
+ process.exit(1);
675
+ }
676
+ console.log(JSON.stringify(result.exports, null, 2));
677
+ });
678
+ program.command("get").description("Get detailed spec for a single export").argument("<entry>", "Entry point file path").argument("<name>", "Export name").action(async (entry, name) => {
679
+ const entryFile = path10.resolve(entry);
680
+ const result = await getExport({ entryFile, exportName: name });
681
+ if (!result.export) {
682
+ const errorMsg = result.errors.length > 0 ? result.errors.join("; ") : `Export '${name}' not found`;
683
+ console.error(JSON.stringify({ error: errorMsg }, null, 2));
684
+ process.exit(1);
685
+ }
686
+ const output = { export: result.export };
687
+ if (result.types.length > 0) {
688
+ output.types = result.types;
689
+ }
690
+ console.log(JSON.stringify(output, null, 2));
691
+ });
692
+ program.addCommand(createSnapshotCommand());
693
+ program.addCommand(createDiffCommand());
694
+ program.addCommand(createDocsCommand());
695
+ program.addCommand(createBreakingCommand());
696
+ program.addCommand(createChangelogCommand());
697
+ program.addCommand(createSemverCommand());
698
+ program.addCommand(createValidateCommand());
699
+ program.addCommand(createDiagnosticsCommand());
700
+ program.addCommand(createFilterCommand());
701
+ program.parse();