@openpkg-ts/cli 0.6.0 → 0.6.2

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.
Files changed (2) hide show
  1. package/dist/bin/openpkg.js +135 -52
  2. package/package.json +4 -4
@@ -9,7 +9,7 @@ import { Command as Command12 } from "commander";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "@openpkg-ts/cli",
12
- version: "0.6.0",
12
+ version: "0.6.2",
13
13
  description: "CLI for OpenPkg TypeScript API extraction and documentation generation",
14
14
  homepage: "https://github.com/ryanwaits/openpkg-ts#readme",
15
15
  repository: {
@@ -32,14 +32,14 @@ var package_default = {
32
32
  test: "bun test"
33
33
  },
34
34
  dependencies: {
35
- "@openpkg-ts/adapters": "^0.3.3",
36
- "@openpkg-ts/sdk": "^0.34.0",
35
+ "@openpkg-ts/adapters": "^0.3.5",
36
+ "@openpkg-ts/sdk": "^0.35.0",
37
37
  commander: "^14.0.0"
38
38
  },
39
39
  devDependencies: {
40
40
  "@types/bun": "latest",
41
41
  "@types/node": "^20.0.0",
42
- bunup: "latest"
42
+ bunup: "^0.16.20"
43
43
  },
44
44
  publishConfig: {
45
45
  access: "public"
@@ -344,11 +344,15 @@ import * as path5 from "node:path";
344
344
  import { loadSpec as loadSpec4, query, toReact } from "@openpkg-ts/sdk";
345
345
  import { Command as Command5 } from "commander";
346
346
  async function readStdin() {
347
- const chunks = [];
348
- for await (const chunk of process.stdin) {
349
- chunks.push(chunk);
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)}`);
350
355
  }
351
- return Buffer.concat(chunks).toString("utf-8");
352
356
  }
353
357
  function getExtension(format) {
354
358
  switch (format) {
@@ -362,14 +366,33 @@ function getExtension(format) {
362
366
  return ".md";
363
367
  }
364
368
  }
369
+ var VALID_KINDS = [
370
+ "function",
371
+ "class",
372
+ "variable",
373
+ "interface",
374
+ "type",
375
+ "enum",
376
+ "module",
377
+ "namespace",
378
+ "reference",
379
+ "external"
380
+ ];
365
381
  function applyFilters(spec, options) {
366
382
  let qb = query(spec);
367
383
  if (options.kind) {
368
- const kinds = options.kind.split(",").map((k) => k.trim());
384
+ const kinds = options.kind.split(",").map((k) => k.trim()).filter(Boolean);
385
+ const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
386
+ if (invalid.length) {
387
+ throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid: ${VALID_KINDS.join(", ")}`);
388
+ }
369
389
  qb = qb.byKind(...kinds);
370
390
  }
371
391
  if (options.tag) {
372
- const tags = options.tag.split(",").map((t) => t.trim());
392
+ const tags = options.tag.split(",").map((t) => t.trim()).filter(Boolean);
393
+ if (tags.length === 0) {
394
+ throw new Error("--tag requires at least one non-empty tag");
395
+ }
373
396
  qb = qb.byTag(...tags);
374
397
  }
375
398
  if (options.search) {
@@ -414,6 +437,13 @@ function createGenerateCommand() {
414
437
  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) => {
415
438
  const format = options.format || "md";
416
439
  try {
440
+ if (options.collapseUnions) {
441
+ const n = parseInt(options.collapseUnions, 10);
442
+ if (Number.isNaN(n) || n < 1) {
443
+ console.error(JSON.stringify({ error: "--collapse-unions must be a positive integer" }));
444
+ process.exit(1);
445
+ }
446
+ }
417
447
  if (options.adapter && options.adapter !== "raw") {
418
448
  let getAdapter;
419
449
  try {
@@ -436,14 +466,24 @@ function createGenerateCommand() {
436
466
  let spec2;
437
467
  if (specPath === "-") {
438
468
  const input = await readStdin();
439
- spec2 = JSON.parse(input);
469
+ try {
470
+ spec2 = JSON.parse(input);
471
+ } catch (err) {
472
+ const msg = err instanceof SyntaxError ? err.message : String(err);
473
+ throw new Error(`Invalid JSON in stdin: ${msg}`);
474
+ }
440
475
  } else {
441
476
  const specFile = path5.resolve(specPath);
442
477
  if (!fs5.existsSync(specFile)) {
443
478
  console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
444
479
  process.exit(1);
445
480
  }
446
- spec2 = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
481
+ try {
482
+ spec2 = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
483
+ } catch (err) {
484
+ const msg = err instanceof SyntaxError ? err.message : String(err);
485
+ throw new Error(`Invalid JSON in ${specPath}: ${msg}`);
486
+ }
447
487
  }
448
488
  spec2 = applyFilters(spec2, options);
449
489
  await adapter.generate(spec2, path5.resolve(options.output));
@@ -453,14 +493,24 @@ function createGenerateCommand() {
453
493
  let spec;
454
494
  if (specPath === "-") {
455
495
  const input = await readStdin();
456
- spec = JSON.parse(input);
496
+ try {
497
+ spec = JSON.parse(input);
498
+ } catch (err) {
499
+ const msg = err instanceof SyntaxError ? err.message : String(err);
500
+ throw new Error(`Invalid JSON in stdin: ${msg}`);
501
+ }
457
502
  } else {
458
503
  const specFile = path5.resolve(specPath);
459
504
  if (!fs5.existsSync(specFile)) {
460
505
  console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
461
506
  process.exit(1);
462
507
  }
463
- spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
508
+ try {
509
+ spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
510
+ } catch (err) {
511
+ const msg = err instanceof SyntaxError ? err.message : String(err);
512
+ throw new Error(`Invalid JSON in ${specPath}: ${msg}`);
513
+ }
464
514
  }
465
515
  spec = applyFilters(spec, options);
466
516
  const docs = loadSpec4(spec);
@@ -511,8 +561,14 @@ Next: Add components with 'openpkg docs add function-section'`);
511
561
  }
512
562
  const exports = docs.getAllExports();
513
563
  for (const exp of exports) {
514
- const filename = `${exp.name}${getExtension(format)}`;
564
+ const filename = path5.basename(`${exp.name}${getExtension(format)}`);
515
565
  const filePath = path5.join(outDir, filename);
566
+ const resolvedPath = path5.resolve(filePath);
567
+ const resolvedOutDir = path5.resolve(outDir);
568
+ if (!resolvedPath.startsWith(resolvedOutDir + path5.sep)) {
569
+ console.error(JSON.stringify({ error: `Path traversal detected: ${exp.name}` }));
570
+ process.exit(1);
571
+ }
516
572
  const content = renderExport(docs, exp.id, format, collapseUnionThreshold);
517
573
  fs5.writeFileSync(filePath, content);
518
574
  }
@@ -545,7 +601,11 @@ function loadComponentsJson() {
545
601
  const configPath = path6.resolve(COMPONENTS_JSON);
546
602
  if (!fs6.existsSync(configPath))
547
603
  return null;
548
- return JSON.parse(fs6.readFileSync(configPath, "utf-8"));
604
+ try {
605
+ return JSON.parse(fs6.readFileSync(configPath, "utf-8"));
606
+ } catch {
607
+ return null;
608
+ }
549
609
  }
550
610
  function createInitCommand() {
551
611
  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) => {
@@ -673,7 +733,7 @@ import {
673
733
  } from "@openpkg-ts/sdk";
674
734
  import { getValidationErrors } from "@openpkg-ts/spec";
675
735
  import { Command as Command11 } from "commander";
676
- var VALID_KINDS = [
736
+ var VALID_KINDS2 = [
677
737
  "function",
678
738
  "class",
679
739
  "variable",
@@ -687,8 +747,24 @@ var VALID_KINDS = [
687
747
  ];
688
748
  function loadSpec6(filePath) {
689
749
  const resolved = path8.resolve(filePath);
690
- const content = fs8.readFileSync(resolved, "utf-8");
691
- return JSON.parse(content);
750
+ let content;
751
+ let spec;
752
+ try {
753
+ content = fs8.readFileSync(resolved, "utf-8");
754
+ } catch (err) {
755
+ throw new Error(`Failed to read spec file: ${err instanceof Error ? err.message : String(err)}`);
756
+ }
757
+ try {
758
+ spec = JSON.parse(content);
759
+ } catch (err) {
760
+ throw new Error(`Invalid JSON in spec file: ${err instanceof Error ? err.message : String(err)}`);
761
+ }
762
+ const errors = getValidationErrors(spec);
763
+ if (errors.length > 0) {
764
+ const details = errors.slice(0, 5).map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
765
+ throw new Error(`Invalid OpenPkg spec: ${details}`);
766
+ }
767
+ return spec;
692
768
  }
693
769
  function parseList(val) {
694
770
  if (!val)
@@ -696,9 +772,9 @@ function parseList(val) {
696
772
  return val.split(",").map((s) => s.trim()).filter(Boolean);
697
773
  }
698
774
  function validateKinds(kinds) {
699
- const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
775
+ const invalid = kinds.filter((k) => !VALID_KINDS2.includes(k));
700
776
  if (invalid.length > 0) {
701
- throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS.join(", ")}`);
777
+ throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS2.join(", ")}`);
702
778
  }
703
779
  return kinds;
704
780
  }
@@ -712,7 +788,7 @@ function formatDiagnostics(diagnostics) {
712
788
  }));
713
789
  }
714
790
  function createSnapshotSubcommand() {
715
- 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) => {
791
+ 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) => {
716
792
  const entryFile = path8.resolve(entry);
717
793
  const entryDir = path8.dirname(entryFile);
718
794
  const fileConfig = loadConfig(entryDir);
@@ -755,6 +831,21 @@ function createSnapshotSubcommand() {
755
831
  ...externalExports.length > 0 && { external: { count: externalExports.length } }
756
832
  };
757
833
  console.error(JSON.stringify(summary, null, 2));
834
+ const extractionWarnings = result.runtimeSchemas?.warnings ?? [];
835
+ if (extractionWarnings.length > 0 && !options.quiet) {
836
+ console.error(`
837
+ Skipped ${extractionWarnings.length} schema(s) with extraction errors:`);
838
+ for (const w of extractionWarnings) {
839
+ console.error(` - ${w.exportName ?? "unknown"}: ${w.code} - ${w.message}`);
840
+ }
841
+ }
842
+ if (options.strict && extractionWarnings.length > 0) {
843
+ console.error(JSON.stringify({
844
+ error: "Extraction warnings present (--strict mode)",
845
+ warnings: extractionWarnings
846
+ }, null, 2));
847
+ process.exit(1);
848
+ }
758
849
  if (options.verify && result.verification && result.verification.failed > 0) {
759
850
  console.error(JSON.stringify({
760
851
  error: "Export verification failed",
@@ -940,34 +1031,26 @@ program.addCommand(createBreakingCommand());
940
1031
  program.addCommand(createChangelogCommand());
941
1032
  program.addCommand(createSemverCommand());
942
1033
  var specCmd = program.commands.find((c) => c.name() === "spec");
943
- var snapshotAlias = new Command12("snapshot").description("(alias) → openpkg spec snapshot").allowUnknownOption().allowExcessArguments().action(async () => {
944
- const args = process.argv.slice(3);
945
- await specCmd.commands.find((c) => c.name() === "snapshot").parseAsync(args, { from: "user" });
946
- });
947
- program.addCommand(snapshotAlias);
948
- var listAlias = new Command12("list").description("(alias) → openpkg spec list").allowUnknownOption().allowExcessArguments().action(async () => {
949
- const args = process.argv.slice(3);
950
- await specCmd.commands.find((c) => c.name() === "list").parseAsync(args, { from: "user" });
951
- });
952
- program.addCommand(listAlias);
953
- var getAlias = new Command12("get").description("(alias) → openpkg spec get").allowUnknownOption().allowExcessArguments().action(async () => {
954
- const args = process.argv.slice(3);
955
- await specCmd.commands.find((c) => c.name() === "get").parseAsync(args, { from: "user" });
956
- });
957
- program.addCommand(getAlias);
958
- var validateAlias = new Command12("validate").description("(alias) → openpkg spec validate").allowUnknownOption().allowExcessArguments().action(async () => {
959
- const args = process.argv.slice(3);
960
- await specCmd.commands.find((c) => c.name() === "validate").parseAsync(args, { from: "user" });
961
- });
962
- program.addCommand(validateAlias);
963
- var filterAlias = new Command12("filter").description("(alias) → openpkg spec filter").allowUnknownOption().allowExcessArguments().action(async () => {
964
- const args = process.argv.slice(3);
965
- await specCmd.commands.find((c) => c.name() === "filter").parseAsync(args, { from: "user" });
966
- });
967
- program.addCommand(filterAlias);
968
- var diagnosticsAlias = new Command12("diagnostics").description("(alias) → openpkg spec lint").allowUnknownOption().allowExcessArguments().action(async () => {
969
- const args = process.argv.slice(3);
970
- await specCmd.commands.find((c) => c.name() === "lint").parseAsync(args, { from: "user" });
971
- });
972
- program.addCommand(diagnosticsAlias);
1034
+ if (!specCmd) {
1035
+ throw new Error("Internal error: spec command not found");
1036
+ }
1037
+ function getSubcommand(parent, name) {
1038
+ const cmd = parent.commands.find((c) => c.name() === name);
1039
+ if (!cmd) {
1040
+ throw new Error(`Internal error: ${name} subcommand not found`);
1041
+ }
1042
+ return cmd;
1043
+ }
1044
+ function createAlias(aliasName, targetName, description) {
1045
+ return new Command12(aliasName).description(description).allowUnknownOption().allowExcessArguments().action(async () => {
1046
+ const args = process.argv.slice(3);
1047
+ await getSubcommand(specCmd, targetName).parseAsync(args, { from: "user" });
1048
+ });
1049
+ }
1050
+ program.addCommand(createAlias("snapshot", "snapshot", "(alias) → openpkg spec snapshot"));
1051
+ program.addCommand(createAlias("list", "list", "(alias) openpkg spec list"));
1052
+ program.addCommand(createAlias("get", "get", "(alias) → openpkg spec get"));
1053
+ program.addCommand(createAlias("validate", "validate", "(alias) → openpkg spec validate"));
1054
+ program.addCommand(createAlias("filter", "filter", "(alias) → openpkg spec filter"));
1055
+ program.addCommand(createAlias("diagnostics", "lint", "(alias) → openpkg spec lint"));
973
1056
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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,14 +23,14 @@
23
23
  "test": "bun test"
24
24
  },
25
25
  "dependencies": {
26
- "@openpkg-ts/adapters": "^0.3.3",
27
- "@openpkg-ts/sdk": "^0.34.0",
26
+ "@openpkg-ts/adapters": "^0.3.5",
27
+ "@openpkg-ts/sdk": "^0.35.0",
28
28
  "commander": "^14.0.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/bun": "latest",
32
32
  "@types/node": "^20.0.0",
33
- "bunup": "latest"
33
+ "bunup": "^0.16.20"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"