@openpkg-ts/cli 0.6.1 → 0.6.3

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.
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env bun
2
2
  import {
3
- __require,
4
- __toESM
5
- } from "../shared/chunk-1dqs11h6.js";
3
+ __require
4
+ } from "../shared/chunk-3s189drz.js";
6
5
 
7
6
  // bin/openpkg.ts
8
7
  import { Command as Command12 } from "commander";
9
8
  // package.json
10
9
  var package_default = {
11
10
  name: "@openpkg-ts/cli",
12
- version: "0.6.1",
11
+ version: "0.6.3",
13
12
  description: "CLI for OpenPkg TypeScript API extraction and documentation generation",
14
13
  homepage: "https://github.com/ryanwaits/openpkg-ts#readme",
15
14
  repository: {
@@ -32,8 +31,8 @@ var package_default = {
32
31
  test: "bun test"
33
32
  },
34
33
  dependencies: {
35
- "@openpkg-ts/adapters": "^0.3.4",
36
- "@openpkg-ts/sdk": "^0.34.1",
34
+ "@openpkg-ts/adapters": "^0.3.14",
35
+ "@openpkg-ts/sdk": "^0.35.1",
37
36
  commander: "^14.0.0"
38
37
  },
39
38
  devDependencies: {
@@ -47,18 +46,66 @@ var package_default = {
47
46
  };
48
47
 
49
48
  // src/commands/breaking.ts
49
+ import { categorizeBreakingChanges, diffSpec } from "@openpkg-ts/spec";
50
+ import { Command } from "commander";
51
+
52
+ // src/commands/utils.ts
50
53
  import * as fs from "node:fs";
51
54
  import * as path from "node:path";
52
- import {
53
- categorizeBreakingChanges,
54
- diffSpec
55
- } from "@openpkg-ts/spec";
56
- import { Command } from "commander";
55
+ import { getValidationErrors } from "@openpkg-ts/spec";
56
+ function handleCommandError(err) {
57
+ const error = err instanceof Error ? err : new Error(String(err));
58
+ console.error(JSON.stringify({ error: error.message }, null, 2));
59
+ process.exit(1);
60
+ }
61
+ async function loadSpecInput(specPath) {
62
+ if (specPath === "-") {
63
+ const chunks = [];
64
+ for await (const chunk of process.stdin) {
65
+ chunks.push(chunk);
66
+ }
67
+ const input = Buffer.concat(chunks).toString("utf-8");
68
+ try {
69
+ return JSON.parse(input);
70
+ } catch (err) {
71
+ const msg = err instanceof SyntaxError ? err.message : String(err);
72
+ throw new Error(`Invalid JSON in stdin: ${msg}`);
73
+ }
74
+ }
75
+ const resolved = path.resolve(specPath);
76
+ if (!fs.existsSync(resolved)) {
77
+ throw new Error(`Spec file not found: ${resolved}`);
78
+ }
79
+ try {
80
+ return JSON.parse(fs.readFileSync(resolved, "utf-8"));
81
+ } catch (err) {
82
+ const msg = err instanceof SyntaxError ? err.message : String(err);
83
+ throw new Error(`Invalid JSON in ${specPath}: ${msg}`);
84
+ }
85
+ }
57
86
  function loadSpec(filePath) {
58
87
  const resolved = path.resolve(filePath);
59
- const content = fs.readFileSync(resolved, "utf-8");
60
- return JSON.parse(content);
88
+ let content;
89
+ let spec;
90
+ try {
91
+ content = fs.readFileSync(resolved, "utf-8");
92
+ } catch (err) {
93
+ throw new Error(`Failed to read spec file: ${err instanceof Error ? err.message : String(err)}`);
94
+ }
95
+ try {
96
+ spec = JSON.parse(content);
97
+ } catch (err) {
98
+ throw new Error(`Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`);
99
+ }
100
+ const errors = getValidationErrors(spec);
101
+ if (errors.length > 0) {
102
+ const details = errors.slice(0, 5).map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
103
+ throw new Error(`Invalid OpenPkg spec: ${details}`);
104
+ }
105
+ return spec;
61
106
  }
107
+
108
+ // src/commands/breaking.ts
62
109
  function createBreakingCommand() {
63
110
  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) => {
64
111
  try {
@@ -75,32 +122,21 @@ function createBreakingCommand() {
75
122
  process.exit(1);
76
123
  }
77
124
  } catch (err) {
78
- const error = err instanceof Error ? err : new Error(String(err));
79
- console.error(JSON.stringify({ error: error.message }, null, 2));
80
- process.exit(1);
125
+ handleCommandError(err);
81
126
  }
82
127
  });
83
128
  }
84
129
 
85
130
  // src/commands/changelog.ts
86
- import * as fs3 from "node:fs";
87
- import * as path3 from "node:path";
88
131
  import { Command as Command3 } from "commander";
89
132
 
90
133
  // src/commands/diff.ts
91
- import * as fs2 from "node:fs";
92
- import * as path2 from "node:path";
93
134
  import {
94
135
  categorizeBreakingChanges as categorizeBreakingChanges2,
95
136
  diffSpec as diffSpec2,
96
137
  recommendSemverBump
97
138
  } from "@openpkg-ts/spec";
98
139
  import { Command as Command2 } from "commander";
99
- function loadSpec2(filePath) {
100
- const resolved = path2.resolve(filePath);
101
- const content = fs2.readFileSync(resolved, "utf-8");
102
- return JSON.parse(content);
103
- }
104
140
  function toExportMap(spec) {
105
141
  const map = new Map;
106
142
  for (const exp of spec.exports) {
@@ -178,8 +214,8 @@ function describeChange(cat) {
178
214
  function createDiffCommand() {
179
215
  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) => {
180
216
  try {
181
- const oldSpec = loadSpec2(oldPath);
182
- const newSpec = loadSpec2(newPath);
217
+ const oldSpec = loadSpec(oldPath);
218
+ const newSpec = loadSpec(newPath);
183
219
  const result = enrichDiff(oldSpec, newSpec);
184
220
  if (options.summary) {
185
221
  console.log(JSON.stringify(result.summary, null, 2));
@@ -187,19 +223,12 @@ function createDiffCommand() {
187
223
  console.log(JSON.stringify(result, null, 2));
188
224
  }
189
225
  } catch (err) {
190
- const error = err instanceof Error ? err : new Error(String(err));
191
- console.error(JSON.stringify({ error: error.message }, null, 2));
192
- process.exit(1);
226
+ handleCommandError(err);
193
227
  }
194
228
  });
195
229
  }
196
230
 
197
231
  // src/commands/changelog.ts
198
- function loadSpec3(filePath) {
199
- const resolved = path3.resolve(filePath);
200
- const content = fs3.readFileSync(resolved, "utf-8");
201
- return JSON.parse(content);
202
- }
203
232
  function formatMarkdown(diff) {
204
233
  const lines = [];
205
234
  if (diff.removed.length > 0 || diff.changed.length > 0) {
@@ -235,8 +264,8 @@ function formatMarkdown(diff) {
235
264
  function createChangelogCommand() {
236
265
  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) => {
237
266
  try {
238
- const oldSpec = loadSpec3(oldPath);
239
- const newSpec = loadSpec3(newPath);
267
+ const oldSpec = loadSpec(oldPath);
268
+ const newSpec = loadSpec(newPath);
240
269
  const diff = enrichDiff(oldSpec, newSpec);
241
270
  if (options.format === "json") {
242
271
  console.log(JSON.stringify(diff, null, 2));
@@ -244,9 +273,7 @@ function createChangelogCommand() {
244
273
  console.log(formatMarkdown(diff));
245
274
  }
246
275
  } catch (err) {
247
- const error = err instanceof Error ? err : new Error(String(err));
248
- console.error(JSON.stringify({ error: error.message }, null, 2));
249
- process.exit(1);
276
+ handleCommandError(err);
250
277
  }
251
278
  });
252
279
  }
@@ -259,13 +286,13 @@ import { spawn } from "node:child_process";
259
286
  import { Command as Command4 } from "commander";
260
287
 
261
288
  // src/commands/docs/utils.ts
262
- import * as fs4 from "node:fs";
263
- import * as path4 from "node:path";
289
+ import * as fs2 from "node:fs";
290
+ import * as path2 from "node:path";
264
291
  function detectPackageManager(cwd = process.cwd()) {
265
- const pkgJsonPath = path4.join(cwd, "package.json");
266
- if (fs4.existsSync(pkgJsonPath)) {
292
+ const pkgJsonPath = path2.join(cwd, "package.json");
293
+ if (fs2.existsSync(pkgJsonPath)) {
267
294
  try {
268
- const pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
295
+ const pkg = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf-8"));
269
296
  if (pkg.packageManager) {
270
297
  if (pkg.packageManager.startsWith("bun"))
271
298
  return "bun";
@@ -278,13 +305,13 @@ function detectPackageManager(cwd = process.cwd()) {
278
305
  }
279
306
  } catch {}
280
307
  }
281
- if (fs4.existsSync(path4.join(cwd, "bun.lockb")) || fs4.existsSync(path4.join(cwd, "bun.lock"))) {
308
+ if (fs2.existsSync(path2.join(cwd, "bun.lockb")) || fs2.existsSync(path2.join(cwd, "bun.lock"))) {
282
309
  return "bun";
283
310
  }
284
- if (fs4.existsSync(path4.join(cwd, "pnpm-lock.yaml"))) {
311
+ if (fs2.existsSync(path2.join(cwd, "pnpm-lock.yaml"))) {
285
312
  return "pnpm";
286
313
  }
287
- if (fs4.existsSync(path4.join(cwd, "yarn.lock"))) {
314
+ if (fs2.existsSync(path2.join(cwd, "yarn.lock"))) {
288
315
  return "yarn";
289
316
  }
290
317
  return "npm";
@@ -339,21 +366,10 @@ function createAddCommand() {
339
366
  }
340
367
 
341
368
  // src/commands/docs/generate.ts
342
- import * as fs5 from "node:fs";
343
- import * as path5 from "node:path";
344
- import { loadSpec as loadSpec4, query, toReact } from "@openpkg-ts/sdk";
369
+ import * as fs3 from "node:fs";
370
+ import * as path3 from "node:path";
371
+ import { loadSpec as loadSpec2, query, toReact } from "@openpkg-ts/sdk";
345
372
  import { Command as Command5 } from "commander";
346
- async function readStdin() {
347
- try {
348
- const chunks = [];
349
- for await (const chunk of process.stdin) {
350
- chunks.push(chunk);
351
- }
352
- return Buffer.concat(chunks).toString("utf-8");
353
- } catch (err) {
354
- throw new Error(`stdin read failed: ${err instanceof Error ? err.message : String(err)}`);
355
- }
356
- }
357
373
  function getExtension(format) {
358
374
  switch (format) {
359
375
  case "json":
@@ -366,14 +382,33 @@ function getExtension(format) {
366
382
  return ".md";
367
383
  }
368
384
  }
385
+ var VALID_KINDS = [
386
+ "function",
387
+ "class",
388
+ "variable",
389
+ "interface",
390
+ "type",
391
+ "enum",
392
+ "module",
393
+ "namespace",
394
+ "reference",
395
+ "external"
396
+ ];
369
397
  function applyFilters(spec, options) {
370
398
  let qb = query(spec);
371
399
  if (options.kind) {
372
- const kinds = options.kind.split(",").map((k) => k.trim());
400
+ const kinds = options.kind.split(",").map((k) => k.trim()).filter(Boolean);
401
+ const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
402
+ if (invalid.length) {
403
+ throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid: ${VALID_KINDS.join(", ")}`);
404
+ }
373
405
  qb = qb.byKind(...kinds);
374
406
  }
375
407
  if (options.tag) {
376
- const tags = options.tag.split(",").map((t) => t.trim());
408
+ const tags = options.tag.split(",").map((t) => t.trim()).filter(Boolean);
409
+ if (tags.length === 0) {
410
+ throw new Error("--tag requires at least one non-empty tag");
411
+ }
377
412
  qb = qb.byTag(...tags);
378
413
  }
379
414
  if (options.search) {
@@ -418,6 +453,13 @@ function createGenerateCommand() {
418
453
  return new Command5("generate").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, react (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)").option("--collapse-unions <n>", "Collapse unions with more than N members").option("-k, --kind <kinds>", "Filter by kind(s), comma-separated").option("-t, --tag <tags>", "Filter by tag(s), comma-separated").option("-s, --search <term>", "Search name and description").option("--deprecated", "Only include deprecated exports").option("--no-deprecated", "Exclude deprecated exports").option("--variant <variant>", "React layout variant: full (single page) or index (links)", "full").option("--components-path <path>", "React components import path", "@/components/api").action(async (specPath, options) => {
419
454
  const format = options.format || "md";
420
455
  try {
456
+ if (options.collapseUnions) {
457
+ const n = parseInt(options.collapseUnions, 10);
458
+ if (Number.isNaN(n) || n < 1) {
459
+ console.error(JSON.stringify({ error: "--collapse-unions must be a positive integer" }));
460
+ process.exit(1);
461
+ }
462
+ }
421
463
  if (options.adapter && options.adapter !== "raw") {
422
464
  let getAdapter;
423
465
  try {
@@ -437,37 +479,15 @@ function createGenerateCommand() {
437
479
  console.error(JSON.stringify({ error: "--adapter requires --output <directory>" }));
438
480
  process.exit(1);
439
481
  }
440
- let spec2;
441
- if (specPath === "-") {
442
- const input = await readStdin();
443
- spec2 = JSON.parse(input);
444
- } else {
445
- const specFile = path5.resolve(specPath);
446
- if (!fs5.existsSync(specFile)) {
447
- console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
448
- process.exit(1);
449
- }
450
- spec2 = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
451
- }
482
+ let spec2 = await loadSpecInput(specPath);
452
483
  spec2 = applyFilters(spec2, options);
453
- await adapter.generate(spec2, path5.resolve(options.output));
484
+ await adapter.generate(spec2, path3.resolve(options.output));
454
485
  console.error(`Generated docs with ${options.adapter} adapter to ${options.output}`);
455
486
  return;
456
487
  }
457
- let spec;
458
- if (specPath === "-") {
459
- const input = await readStdin();
460
- spec = JSON.parse(input);
461
- } else {
462
- const specFile = path5.resolve(specPath);
463
- if (!fs5.existsSync(specFile)) {
464
- console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
465
- process.exit(1);
466
- }
467
- spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
468
- }
488
+ let spec = await loadSpecInput(specPath);
469
489
  spec = applyFilters(spec, options);
470
- const docs = loadSpec4(spec);
490
+ const docs = loadSpec2(spec);
471
491
  const collapseUnionThreshold = options.collapseUnions ? parseInt(options.collapseUnions, 10) : undefined;
472
492
  if (format === "react") {
473
493
  if (!options.output) {
@@ -476,7 +496,7 @@ function createGenerateCommand() {
476
496
  }
477
497
  const variant = options.variant === "index" ? "index" : "full";
478
498
  await toReact(spec, {
479
- outDir: path5.resolve(options.output),
499
+ outDir: path3.resolve(options.output),
480
500
  variant,
481
501
  componentsPath: options.componentsPath ?? "@/components/api"
482
502
  });
@@ -496,8 +516,8 @@ Next: Add components with 'openpkg docs add function-section'`);
496
516
  }
497
517
  const output2 = renderExport(docs, exp.id, format, collapseUnionThreshold);
498
518
  if (options.output && options.output !== "-") {
499
- const outputPath = path5.resolve(options.output);
500
- fs5.writeFileSync(outputPath, output2);
519
+ const outputPath = path3.resolve(options.output);
520
+ fs3.writeFileSync(outputPath, output2);
501
521
  console.error(`Wrote ${outputPath}`);
502
522
  } else {
503
523
  console.log(output2);
@@ -509,59 +529,61 @@ Next: Add components with 'openpkg docs add function-section'`);
509
529
  console.error(JSON.stringify({ error: "--split requires --output <directory>" }));
510
530
  process.exit(1);
511
531
  }
512
- const outDir = path5.resolve(options.output);
513
- if (!fs5.existsSync(outDir)) {
514
- fs5.mkdirSync(outDir, { recursive: true });
532
+ const outDir = path3.resolve(options.output);
533
+ if (!fs3.existsSync(outDir)) {
534
+ fs3.mkdirSync(outDir, { recursive: true });
515
535
  }
516
536
  const exports = docs.getAllExports();
517
537
  for (const exp of exports) {
518
- const filename = path5.basename(`${exp.name}${getExtension(format)}`);
519
- const filePath = path5.join(outDir, filename);
520
- const resolvedPath = path5.resolve(filePath);
521
- const resolvedOutDir = path5.resolve(outDir);
522
- if (!resolvedPath.startsWith(resolvedOutDir + path5.sep)) {
538
+ const filename = path3.basename(`${exp.name}${getExtension(format)}`);
539
+ const filePath = path3.join(outDir, filename);
540
+ const resolvedPath = path3.resolve(filePath);
541
+ const resolvedOutDir = path3.resolve(outDir);
542
+ if (!resolvedPath.startsWith(resolvedOutDir + path3.sep)) {
523
543
  console.error(JSON.stringify({ error: `Path traversal detected: ${exp.name}` }));
524
544
  process.exit(1);
525
545
  }
526
546
  const content = renderExport(docs, exp.id, format, collapseUnionThreshold);
527
- fs5.writeFileSync(filePath, content);
547
+ fs3.writeFileSync(filePath, content);
528
548
  }
529
549
  console.error(`Wrote ${exports.length} files to ${outDir}`);
530
550
  return;
531
551
  }
532
552
  const output = renderFull(docs, format, collapseUnionThreshold);
533
553
  if (options.output && options.output !== "-") {
534
- const outputPath = path5.resolve(options.output);
535
- fs5.writeFileSync(outputPath, output);
554
+ const outputPath = path3.resolve(options.output);
555
+ fs3.writeFileSync(outputPath, output);
536
556
  console.error(`Wrote ${outputPath}`);
537
557
  } else {
538
558
  console.log(output);
539
559
  }
540
560
  } catch (err) {
541
- const error = err instanceof Error ? err : new Error(String(err));
542
- console.error(JSON.stringify({ error: error.message }));
543
- process.exit(1);
561
+ handleCommandError(err);
544
562
  }
545
563
  });
546
564
  }
547
565
 
548
566
  // src/commands/docs/init.ts
549
- import * as fs6 from "node:fs";
550
- import * as path6 from "node:path";
567
+ import * as fs4 from "node:fs";
568
+ import * as path4 from "node:path";
551
569
  import { Command as Command6 } from "commander";
552
570
  var COMPONENTS_JSON = "components.json";
553
571
  var REGISTRY_URL = "https://raw.githubusercontent.com/anthropics/openpkg-ts/main/registry/r/{name}.json";
554
572
  function loadComponentsJson() {
555
- const configPath = path6.resolve(COMPONENTS_JSON);
556
- if (!fs6.existsSync(configPath))
573
+ const configPath = path4.resolve(COMPONENTS_JSON);
574
+ if (!fs4.existsSync(configPath))
575
+ return null;
576
+ try {
577
+ return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
578
+ } catch {
557
579
  return null;
558
- return JSON.parse(fs6.readFileSync(configPath, "utf-8"));
580
+ }
559
581
  }
560
582
  function createInitCommand() {
561
583
  return new Command6("init").description("Add @openpkg registry to components.json for shadcn CLI").option("--registry <url>", "Custom registry URL", REGISTRY_URL).action(async (options) => {
562
- const configPath = path6.resolve(COMPONENTS_JSON);
584
+ const configPath = path4.resolve(COMPONENTS_JSON);
563
585
  const registryUrl = options.registry || REGISTRY_URL;
564
- if (!fs6.existsSync(configPath)) {
586
+ if (!fs4.existsSync(configPath)) {
565
587
  console.error(`${COMPONENTS_JSON} not found.`);
566
588
  console.error('Run "npx shadcn@latest init" first to initialize shadcn.');
567
589
  process.exit(1);
@@ -573,7 +595,7 @@ function createInitCommand() {
573
595
  }
574
596
  config.registries = config.registries || {};
575
597
  config.registries["@openpkg"] = registryUrl;
576
- fs6.writeFileSync(configPath, JSON.stringify(config, null, 2));
598
+ fs4.writeFileSync(configPath, JSON.stringify(config, null, 2));
577
599
  console.log(`Added @openpkg registry to ${COMPONENTS_JSON}`);
578
600
  console.log("");
579
601
  console.log("Usage:");
@@ -640,20 +662,13 @@ function createDocsCommand() {
640
662
  }
641
663
 
642
664
  // src/commands/semver.ts
643
- import * as fs7 from "node:fs";
644
- import * as path7 from "node:path";
645
665
  import { diffSpec as diffSpec3, recommendSemverBump as recommendSemverBump2 } from "@openpkg-ts/spec";
646
666
  import { Command as Command10 } from "commander";
647
- function loadSpec5(filePath) {
648
- const resolved = path7.resolve(filePath);
649
- const content = fs7.readFileSync(resolved, "utf-8");
650
- return JSON.parse(content);
651
- }
652
667
  function createSemverCommand() {
653
668
  return new Command10("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) => {
654
669
  try {
655
- const oldSpec = loadSpec5(oldPath);
656
- const newSpec = loadSpec5(newPath);
670
+ const oldSpec = loadSpec(oldPath);
671
+ const newSpec = loadSpec(newPath);
657
672
  const diff = diffSpec3(oldSpec, newSpec);
658
673
  const recommendation = recommendSemverBump2(diff);
659
674
  const result = {
@@ -662,16 +677,14 @@ function createSemverCommand() {
662
677
  };
663
678
  console.log(JSON.stringify(result, null, 2));
664
679
  } catch (err) {
665
- const error = err instanceof Error ? err : new Error(String(err));
666
- console.error(JSON.stringify({ error: error.message }, null, 2));
667
- process.exit(1);
680
+ handleCommandError(err);
668
681
  }
669
682
  });
670
683
  }
671
684
 
672
685
  // src/commands/spec.ts
673
- import * as fs8 from "node:fs";
674
- import * as path8 from "node:path";
686
+ import * as fs5 from "node:fs";
687
+ import * as path5 from "node:path";
675
688
  import {
676
689
  analyzeSpec,
677
690
  extractSpec,
@@ -681,9 +694,9 @@ import {
681
694
  loadConfig,
682
695
  mergeConfig
683
696
  } from "@openpkg-ts/sdk";
684
- import { getValidationErrors } from "@openpkg-ts/spec";
697
+ import { getValidationErrors as getValidationErrors2 } from "@openpkg-ts/spec";
685
698
  import { Command as Command11 } from "commander";
686
- var VALID_KINDS = [
699
+ var VALID_KINDS2 = [
687
700
  "function",
688
701
  "class",
689
702
  "variable",
@@ -695,36 +708,15 @@ var VALID_KINDS = [
695
708
  "reference",
696
709
  "external"
697
710
  ];
698
- function loadSpec6(filePath) {
699
- const resolved = path8.resolve(filePath);
700
- let content;
701
- let spec;
702
- try {
703
- content = fs8.readFileSync(resolved, "utf-8");
704
- } catch (err) {
705
- throw new Error(`Failed to read spec file: ${err instanceof Error ? err.message : String(err)}`);
706
- }
707
- try {
708
- spec = JSON.parse(content);
709
- } catch (err) {
710
- throw new Error(`Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`);
711
- }
712
- const errors = getValidationErrors(spec);
713
- if (errors.length > 0) {
714
- const details = errors.slice(0, 5).map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
715
- throw new Error(`Invalid OpenPkg spec: ${details}`);
716
- }
717
- return spec;
718
- }
719
711
  function parseList(val) {
720
712
  if (!val)
721
713
  return;
722
714
  return val.split(",").map((s) => s.trim()).filter(Boolean);
723
715
  }
724
716
  function validateKinds(kinds) {
725
- const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
717
+ const invalid = kinds.filter((k) => !VALID_KINDS2.includes(k));
726
718
  if (invalid.length > 0) {
727
- throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS.join(", ")}`);
719
+ throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS2.join(", ")}`);
728
720
  }
729
721
  return kinds;
730
722
  }
@@ -738,9 +730,9 @@ function formatDiagnostics(diagnostics) {
738
730
  }));
739
731
  }
740
732
  function createSnapshotSubcommand() {
741
- return new Command11("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)", "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)").option("--ignore <exports>", "Ignore exports (comma-separated)").option("--verify", "Exit 1 if any exports fail").option("--verbose", "Show detailed output").option("--include-private", "Include private/protected class members").option("--external-include <patterns...>", "Resolve re-exports from these packages").option("--external-exclude <patterns...>", "Never resolve from these packages").option("--external-depth <n>", "Max transitive depth for external resolution", "1").action(async (entry, options) => {
742
- const entryFile = path8.resolve(entry);
743
- const entryDir = path8.dirname(entryFile);
733
+ return new Command11("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)", "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)").option("--ignore <exports>", "Ignore exports (comma-separated)").option("--verify", "Exit 1 if any exports fail").option("--verbose", "Show detailed output").option("--quiet", "Suppress extraction warnings").option("--strict", "Exit 1 if any extraction warnings").option("--include-private", "Include private/protected class members").option("--external-include <patterns...>", "Resolve re-exports from these packages").option("--external-exclude <patterns...>", "Never resolve from these packages").option("--external-depth <n>", "Max transitive depth for external resolution", "1").action(async (entry, options) => {
734
+ const entryFile = path5.resolve(entry);
735
+ const entryDir = path5.dirname(entryFile);
744
736
  const fileConfig = loadConfig(entryDir);
745
737
  const cliConfig = options.externalInclude ? {
746
738
  externals: {
@@ -781,6 +773,21 @@ function createSnapshotSubcommand() {
781
773
  ...externalExports.length > 0 && { external: { count: externalExports.length } }
782
774
  };
783
775
  console.error(JSON.stringify(summary, null, 2));
776
+ const extractionWarnings = result.runtimeSchemas?.warnings ?? [];
777
+ if (extractionWarnings.length > 0 && !options.quiet) {
778
+ console.error(`
779
+ Skipped ${extractionWarnings.length} schema(s) with extraction errors:`);
780
+ for (const w of extractionWarnings) {
781
+ console.error(` - ${w.exportName ?? "unknown"}: ${w.code} - ${w.message}`);
782
+ }
783
+ }
784
+ if (options.strict && extractionWarnings.length > 0) {
785
+ console.error(JSON.stringify({
786
+ error: "Extraction warnings present (--strict mode)",
787
+ warnings: extractionWarnings
788
+ }, null, 2));
789
+ process.exit(1);
790
+ }
784
791
  if (options.verify && result.verification && result.verification.failed > 0) {
785
792
  console.error(JSON.stringify({
786
793
  error: "Export verification failed",
@@ -793,37 +800,33 @@ function createSnapshotSubcommand() {
793
800
  if (options.output === "-") {
794
801
  console.log(specJson);
795
802
  } else {
796
- const outputPath = path8.resolve(options.output ?? "openpkg.json");
797
- fs8.writeFileSync(outputPath, specJson);
803
+ const outputPath = path5.resolve(options.output ?? "openpkg.json");
804
+ fs5.writeFileSync(outputPath, specJson);
798
805
  console.error(`Wrote ${outputPath}`);
799
806
  }
800
807
  } catch (err) {
801
- const error = err instanceof Error ? err : new Error(String(err));
802
- console.error(JSON.stringify({ error: error.message }, null, 2));
803
- process.exit(1);
808
+ handleCommandError(err);
804
809
  }
805
810
  });
806
811
  }
807
812
  function createValidateSubcommand() {
808
813
  return new Command11("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) => {
809
814
  try {
810
- const spec = loadSpec6(specPath);
815
+ const spec = loadSpec(specPath);
811
816
  const version = options.version ?? "latest";
812
- const errors = getValidationErrors(spec, version);
817
+ const errors = getValidationErrors2(spec, version);
813
818
  console.log(JSON.stringify({ valid: errors.length === 0, errors }, null, 2));
814
819
  if (errors.length > 0)
815
820
  process.exit(1);
816
821
  } catch (err) {
817
- const error = err instanceof Error ? err : new Error(String(err));
818
- console.error(JSON.stringify({ error: error.message }, null, 2));
819
- process.exit(1);
822
+ handleCommandError(err);
820
823
  }
821
824
  });
822
825
  }
823
826
  function createFilterSubcommand() {
824
827
  return new Command11("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").option("--search-members", "Also search member names/descriptions").option("--search-docs", "Also search param/return descriptions").option("--module <path>", "Filter by source file path").option("-o, --output <file>", "Output file (default: stdout)").option("--summary", "Only output matched/total counts").option("--quiet", "Output raw spec only").action(async (specPath, options) => {
825
828
  try {
826
- const spec = loadSpec6(specPath);
829
+ const spec = loadSpec(specPath);
827
830
  const criteria = {};
828
831
  if (options.kind) {
829
832
  const kinds = parseList(options.kind);
@@ -861,21 +864,19 @@ function createFilterSubcommand() {
861
864
  }
862
865
  const json = JSON.stringify(output, null, 2);
863
866
  if (options.output) {
864
- fs8.writeFileSync(path8.resolve(options.output), json);
867
+ fs5.writeFileSync(path5.resolve(options.output), json);
865
868
  } else {
866
869
  console.log(json);
867
870
  }
868
871
  } catch (err) {
869
- const error = err instanceof Error ? err : new Error(String(err));
870
- console.error(JSON.stringify({ error: error.message }, null, 2));
871
- process.exit(1);
872
+ handleCommandError(err);
872
873
  }
873
874
  });
874
875
  }
875
876
  function createLintSubcommand() {
876
877
  return new Command11("lint").description("Analyze spec for quality issues (missing docs, deprecated without reason)").argument("<spec>", "Path to spec file (JSON)").option("--verbose", "Show detailed information").action(async (specPath, options) => {
877
878
  try {
878
- const spec = loadSpec6(specPath);
879
+ const spec = loadSpec(specPath);
879
880
  const diagnostics = analyzeSpec(spec);
880
881
  const generation = spec.generation;
881
882
  const skipped = generation?.skipped ?? [];
@@ -912,15 +913,13 @@ function createLintSubcommand() {
912
913
  };
913
914
  console.log(JSON.stringify(result, null, 2));
914
915
  } catch (err) {
915
- const error = err instanceof Error ? err : new Error(String(err));
916
- console.error(JSON.stringify({ error: error.message }, null, 2));
917
- process.exit(1);
916
+ handleCommandError(err);
918
917
  }
919
918
  });
920
919
  }
921
920
  function createListSubcommand() {
922
921
  return new Command11("list").description("List exports from a TypeScript entry point").argument("<entry>", "Entry point file path").action(async (entry) => {
923
- const entryFile = path8.resolve(entry);
922
+ const entryFile = path5.resolve(entry);
924
923
  const result = await listExports({ entryFile });
925
924
  if (result.errors.length > 0) {
926
925
  console.error(JSON.stringify({ errors: result.errors }, null, 2));
@@ -931,7 +930,7 @@ function createListSubcommand() {
931
930
  }
932
931
  function createGetSubcommand() {
933
932
  return new Command11("get").description("Get detailed spec for a single export").argument("<entry>", "Entry point file path").argument("<name>", "Export name").action(async (entry, name) => {
934
- const entryFile = path8.resolve(entry);
933
+ const entryFile = path5.resolve(entry);
935
934
  const result = await getExport({ entryFile, exportName: name });
936
935
  if (!result.export) {
937
936
  const errorMsg = result.errors.length > 0 ? result.errors.join("; ") : `Export '${name}' not found`;
@@ -0,0 +1,4 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ export { __require };
@@ -1,3 +1,42 @@
1
1
  import { getExport, listExports } from "@openpkg-ts/sdk";
2
+ import { CategorizedBreaking } from "@openpkg-ts/spec";
3
+ type BreakingResult = {
4
+ breaking: CategorizedBreaking[];
5
+ count: number;
6
+ };
7
+ import { CategorizedBreaking as CategorizedBreaking2, SemverBump, SpecExportKind } from "@openpkg-ts/spec";
8
+ /**
9
+ * A changed with details about what changed
10
+ */
11
+ interface ChangedExport {
12
+ id: string;
13
+ name: string;
14
+ kind: SpecExportKind;
15
+ description: string;
16
+ }
17
+ /**
18
+ * Enriched diff result with categorized changes
19
+ */
20
+ interface DiffResult {
21
+ breaking: CategorizedBreaking2[];
22
+ added: string[];
23
+ removed: RemovedExport[];
24
+ changed: ChangedExport[];
25
+ docsOnly: string[];
26
+ summary: {
27
+ breakingCount: number;
28
+ addedCount: number;
29
+ removedCount: number;
30
+ changedCount: number;
31
+ docsOnlyCount: number;
32
+ semverBump: SemverBump;
33
+ semverReason: string;
34
+ };
35
+ }
36
+ interface RemovedExport {
37
+ id: string;
38
+ name: string;
39
+ kind: SpecExportKind;
40
+ }
2
41
  import { FilterResult, FilterSummaryResult } from "./commands/filter";
3
- export { listExports, getExport, FilterSummaryResult, FilterResult };
42
+ export { listExports, getExport, RemovedExport, FilterSummaryResult, FilterResult, DiffResult, ChangedExport, BreakingResult };
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import"../shared/chunk-1dqs11h6.js";
1
+ import"../shared/chunk-3s189drz.js";
2
2
 
3
3
  // src/index.ts
4
4
  import { getExport, listExports } from "@openpkg-ts/sdk";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "CLI for OpenPkg TypeScript API extraction and documentation generation",
5
5
  "homepage": "https://github.com/ryanwaits/openpkg-ts#readme",
6
6
  "repository": {
@@ -23,8 +23,8 @@
23
23
  "test": "bun test"
24
24
  },
25
25
  "dependencies": {
26
- "@openpkg-ts/adapters": "^0.3.4",
27
- "@openpkg-ts/sdk": "^0.34.1",
26
+ "@openpkg-ts/adapters": "^0.3.14",
27
+ "@openpkg-ts/sdk": "^0.35.1",
28
28
  "commander": "^14.0.0"
29
29
  },
30
30
  "devDependencies": {
@@ -1,20 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
16
- return to;
17
- };
18
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
-
20
- export { __toESM, __require };