@openpkg-ts/cli 0.5.1 → 0.6.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/README.md CHANGED
@@ -10,11 +10,24 @@ npm install -g @openpkg-ts/cli
10
10
  npx @openpkg-ts/cli <command>
11
11
  ```
12
12
 
13
- ## Commands
13
+ ## Command Structure
14
14
 
15
- ### list
15
+ Commands organized under `openpkg spec` and `openpkg docs`. Legacy commands work as aliases.
16
+
17
+ ```bash
18
+ openpkg spec snapshot ./src/index.ts -o spec.json
19
+ openpkg spec validate spec.json
20
+
21
+ openpkg docs init
22
+ openpkg docs generate spec.json -o ./docs
23
+ openpkg docs add function-section
24
+ ```
25
+
26
+ ---
16
27
 
17
- List exports from entry point.
28
+ ## Spec Commands
29
+
30
+ ### list
18
31
 
19
32
  ```bash
20
33
  openpkg list src/index.ts
@@ -24,31 +37,21 @@ Output: JSON array of `{ name, kind, file, line, description }`
24
37
 
25
38
  ### get
26
39
 
27
- Get detailed spec for single export.
28
-
29
40
  ```bash
30
41
  openpkg get src/index.ts createClient
31
42
  ```
32
43
 
33
44
  Output: JSON with `{ export, types }` - full spec for the export plus referenced types.
34
45
 
35
- ### snapshot
36
-
37
- Generate full OpenPkg spec from TypeScript.
46
+ ### spec snapshot
38
47
 
39
48
  ```bash
40
- # Write to file
41
- openpkg snapshot src/index.ts -o openpkg.json
42
-
43
- # Stdout (pipeable)
44
- openpkg snapshot src/index.ts -o -
45
-
46
- # With options
47
- openpkg snapshot src/index.ts --max-depth 4 --runtime --verify
48
- openpkg snapshot src/index.ts --only "use*,create*" --ignore "*Internal"
49
+ openpkg spec snapshot src/index.ts -o openpkg.json
50
+ openpkg spec snapshot src/index.ts -o - # stdout
51
+ openpkg spec snapshot src/index.ts --max-depth 4 --runtime --verify
52
+ openpkg spec snapshot src/index.ts --only "use*,create*" --ignore "*Internal"
49
53
  ```
50
54
 
51
- Options:
52
55
  | Flag | Description |
53
56
  |------|-------------|
54
57
  | `-o, --output <file>` | Output file (default: openpkg.json, `-` for stdout) |
@@ -59,195 +62,161 @@ Options:
59
62
  | `--ignore <exports>` | Ignore exports (comma-separated, wildcards) |
60
63
  | `--verify` | Exit 1 if any exports fail |
61
64
 
62
- ### docs
63
-
64
- Generate documentation from spec.
65
+ ### spec validate
65
66
 
66
67
  ```bash
67
- # Markdown (default)
68
- openpkg docs openpkg.json -o api.md
69
-
70
- # HTML
71
- openpkg docs openpkg.json -f html -o api.html
68
+ openpkg spec validate openpkg.json
69
+ openpkg spec validate openpkg.json --version 1.0
70
+ ```
72
71
 
73
- # JSON (simplified structure)
74
- openpkg docs openpkg.json -f json
72
+ ### spec diagnostics
75
73
 
76
- # Split: one file per export
77
- openpkg docs openpkg.json --split -o docs/api/
74
+ ```bash
75
+ openpkg spec diagnostics openpkg.json
76
+ ```
78
77
 
79
- # Pipeline: stdin
80
- openpkg snapshot src/index.ts -o - | openpkg docs - -f md
78
+ ### spec filter
81
79
 
82
- # With adapter (generates framework-specific output)
83
- openpkg docs openpkg.json --adapter fumadocs -o docs/api/
80
+ ```bash
81
+ openpkg spec filter openpkg.json --kind function,class
82
+ openpkg spec filter openpkg.json --has-description -o documented.json
83
+ openpkg spec filter openpkg.json --search "user" --summary
84
+ openpkg spec filter openpkg.json --deprecated --quiet | jq '.exports[].name'
84
85
  ```
85
86
 
86
- Options:
87
87
  | Flag | Description |
88
88
  |------|-------------|
89
- | `-o, --output <path>` | Output file or directory (default: stdout) |
90
- | `-f, --format <fmt>` | Format: `md`, `json`, `html` (default: md) |
91
- | `--split` | One file per export (requires `-o` as directory) |
92
- | `-a, --adapter <name>` | Use adapter: `fumadocs`, `raw` (default: raw) |
93
-
94
- ### diff
89
+ | `--kind <kinds>` | Filter by kinds (comma-separated) |
90
+ | `--name <names>` | Filter by exact names (comma-separated) |
91
+ | `--id <ids>` | Filter by export IDs (comma-separated) |
92
+ | `--tag <tags>` | Filter by tags (comma-separated) |
93
+ | `--deprecated` | Only deprecated exports |
94
+ | `--no-deprecated` | Exclude deprecated exports |
95
+ | `--has-description` | Only exports with descriptions |
96
+ | `--missing-description` | Only exports without descriptions |
97
+ | `--search <term>` | Search name/description (case-insensitive) |
98
+ | `--module <path>` | Filter by source file path (contains) |
99
+ | `-o, --output <file>` | Output file (default: stdout) |
100
+ | `--summary` | Only output matched/total counts |
101
+ | `--quiet` | Output raw spec only (no wrapper) |
95
102
 
96
- Compare two specs for breaking changes.
103
+ ### spec diff
97
104
 
98
105
  ```bash
99
- openpkg diff old.json new.json
100
- openpkg diff old.json new.json --summary
106
+ openpkg spec diff old.json new.json
107
+ openpkg spec diff old.json new.json --summary
101
108
  ```
102
109
 
103
- Output includes:
104
- - `breaking` - categorized breaking changes
105
- - `added` - new exports
106
- - `removed` - removed exports
107
- - `changed` - modified exports
108
- - `docsOnly` - documentation-only changes
109
- - `summary.semverBump` - recommended version bump
110
+ ### spec breaking
110
111
 
111
- ### breaking
112
-
113
- Check for breaking changes. Exit 1 if any found.
112
+ Exit 1 if breaking changes found.
114
113
 
115
114
  ```bash
116
- openpkg breaking old.json new.json
115
+ openpkg spec breaking old.json new.json
117
116
  ```
118
117
 
119
- Output:
120
- ```json
121
- {
122
- "breaking": [
123
- { "id": "createClient", "name": "createClient", "kind": "function", "severity": "high", "reason": "signature changed" }
124
- ],
125
- "count": 1
126
- }
127
- ```
118
+ ### spec semver
128
119
 
129
- ### semver
120
+ ```bash
121
+ openpkg spec semver old.json new.json
122
+ ```
130
123
 
131
- Recommend version bump based on changes.
124
+ ### spec changelog
132
125
 
133
126
  ```bash
134
- openpkg semver old.json new.json
127
+ openpkg spec changelog old.json new.json
128
+ openpkg spec changelog old.json new.json --format json
135
129
  ```
136
130
 
137
- Output:
138
- ```json
139
- {
140
- "bump": "major",
141
- "reason": "1 breaking change detected"
142
- }
143
- ```
131
+ ---
144
132
 
145
- ### validate
133
+ ## Docs Commands
146
134
 
147
- Validate spec against schema.
135
+ ### docs init
136
+
137
+ Initialize docs configuration.
148
138
 
149
139
  ```bash
150
- openpkg validate openpkg.json
151
- openpkg validate openpkg.json --version 1.0
140
+ openpkg docs init
152
141
  ```
153
142
 
154
- Output:
155
- ```json
156
- {
157
- "valid": true,
158
- "errors": []
159
- }
160
- ```
143
+ Creates `openpkg.config.json` with default settings.
161
144
 
162
- ### changelog
145
+ ### docs generate
163
146
 
164
- Generate changelog from diff.
147
+ Generate documentation from spec.
165
148
 
166
149
  ```bash
167
- openpkg changelog old.json new.json
168
- openpkg changelog old.json new.json --format json
169
- ```
150
+ # Markdown (default)
151
+ openpkg docs generate openpkg.json -o api.md
170
152
 
171
- Markdown output:
172
- ```markdown
173
- ## Breaking Changes
174
- - **Removed** `oldFunction` (function)
153
+ # React layout (single layout + spec JSON, add components via registry)
154
+ openpkg docs generate openpkg.json -f react -o ./app/api
175
155
 
176
- ## Added
177
- - `newFunction`
178
- ```
156
+ # HTML
157
+ openpkg docs generate openpkg.json -f html -o api.html
179
158
 
180
- ### diagnostics
159
+ # JSON (simplified structure)
160
+ openpkg docs generate openpkg.json -f json
181
161
 
182
- Analyze spec for quality issues.
162
+ # Split: one file per export
163
+ openpkg docs generate openpkg.json --split -o docs/api/
183
164
 
184
- ```bash
185
- openpkg diagnostics openpkg.json
165
+ # With adapter
166
+ openpkg docs generate openpkg.json -a fumadocs -o docs/api/
167
+
168
+ # From stdin
169
+ openpkg spec snapshot src/index.ts -o - | openpkg docs generate - -f md
186
170
  ```
187
171
 
188
- Output:
189
- ```json
190
- {
191
- "summary": {
192
- "total": 5,
193
- "missingDescriptions": 3,
194
- "deprecatedNoReason": 1,
195
- "missingParamDocs": 1
196
- },
197
- "diagnostics": { ... }
198
- }
172
+ | Flag | Description |
173
+ |------|-------------|
174
+ | `-o, --output <path>` | Output file or directory (default: stdout) |
175
+ | `-f, --format <fmt>` | Format: `md`, `json`, `html`, `react` (default: md) |
176
+ | `--split` | One file per export (requires `-o` as directory) |
177
+ | `-a, --adapter <name>` | Use adapter: `fumadocs`, `raw` (default: raw) |
178
+
179
+ ### docs add
180
+
181
+ Add components from shadcn-compatible registry.
182
+
183
+ ```bash
184
+ openpkg docs add function-section
185
+ openpkg docs add class-section interface-section
186
+ openpkg docs add export-card param-table signature
199
187
  ```
200
188
 
201
- ### filter
189
+ ### docs list
202
190
 
203
- Filter spec by various criteria.
191
+ List available registry components.
204
192
 
205
193
  ```bash
206
- openpkg filter openpkg.json --kind function,class
207
- openpkg filter openpkg.json --has-description -o documented.json
208
- openpkg filter openpkg.json --search "user" --summary
209
- openpkg filter openpkg.json --deprecated --quiet | jq '.exports[].name'
194
+ openpkg docs list
210
195
  ```
211
196
 
212
- Options:
213
- | Flag | Description |
214
- |------|-------------|
215
- | `--kind <kinds>` | Filter by kinds (comma-separated) |
216
- | `--name <names>` | Filter by exact names (comma-separated) |
217
- | `--id <ids>` | Filter by export IDs (comma-separated) |
218
- | `--tag <tags>` | Filter by tags (comma-separated) |
219
- | `--deprecated` | Only deprecated exports |
220
- | `--no-deprecated` | Exclude deprecated exports |
221
- | `--has-description` | Only exports with descriptions |
222
- | `--missing-description` | Only exports without descriptions |
223
- | `--search <term>` | Search name/description (case-insensitive) |
224
- | `--module <path>` | Filter by source file path (contains) |
225
- | `-o, --output <file>` | Output file (default: stdout) |
226
- | `--summary` | Only output matched/total counts |
227
- | `--quiet` | Output raw spec only (no wrapper) |
197
+ 16 components available: layouts, sections, primitives.
198
+
199
+ ### docs view
228
200
 
229
- All criteria use AND logic when combined.
201
+ View component details and dependencies.
230
202
 
231
- Output (default):
232
- ```json
233
- {
234
- "spec": { ... },
235
- "matched": 12,
236
- "total": 45
237
- }
203
+ ```bash
204
+ openpkg docs view function-section
238
205
  ```
239
206
 
207
+ ---
208
+
240
209
  ## Pipelines
241
210
 
242
211
  Commands are composable via stdin/stdout:
243
212
 
244
213
  ```bash
245
214
  # Extract and generate docs
246
- openpkg snapshot src/index.ts -o - | openpkg docs - -f md > api.md
215
+ openpkg spec snapshot src/index.ts -o - | openpkg docs generate - -f md > api.md
247
216
 
248
217
  # Extract, verify, then diff
249
- openpkg snapshot src/index.ts --verify -o new.json
250
- openpkg diff baseline.json new.json --summary
218
+ openpkg spec snapshot src/index.ts --verify -o new.json
219
+ openpkg spec diff baseline.json new.json --summary
251
220
  ```
252
221
 
253
222
  ## Programmatic Use
@@ -5,13 +5,11 @@ import {
5
5
  } from "../shared/chunk-1dqs11h6.js";
6
6
 
7
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";
8
+ import { Command as Command12 } from "commander";
11
9
  // package.json
12
10
  var package_default = {
13
11
  name: "@openpkg-ts/cli",
14
- version: "0.5.1",
12
+ version: "0.6.0",
15
13
  description: "CLI for OpenPkg TypeScript API extraction and documentation generation",
16
14
  homepage: "https://github.com/ryanwaits/openpkg-ts#readme",
17
15
  repository: {
@@ -34,8 +32,8 @@ var package_default = {
34
32
  test: "bun test"
35
33
  },
36
34
  dependencies: {
37
- "@openpkg-ts/adapters": "^0.3.2",
38
- "@openpkg-ts/sdk": "^0.33.1",
35
+ "@openpkg-ts/adapters": "^0.3.3",
36
+ "@openpkg-ts/sdk": "^0.34.0",
39
37
  commander: "^14.0.0"
40
38
  },
41
39
  devDependencies: {
@@ -253,71 +251,97 @@ function createChangelogCommand() {
253
251
  });
254
252
  }
255
253
 
256
- // src/commands/diagnostics.ts
254
+ // src/commands/docs/index.ts
255
+ import { Command as Command9 } from "commander";
256
+
257
+ // src/commands/docs/add.ts
258
+ import { spawn } from "node:child_process";
259
+ import { Command as Command4 } from "commander";
260
+
261
+ // src/commands/docs/utils.ts
257
262
  import * as fs4 from "node:fs";
258
263
  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)").option("--verbose", "Show detailed information including skipped export details").action(async (specPath, options) => {
264
+ function detectPackageManager(cwd = process.cwd()) {
265
+ const pkgJsonPath = path4.join(cwd, "package.json");
266
+ if (fs4.existsSync(pkgJsonPath)) {
268
267
  try {
269
- const spec = loadJSON(specPath);
270
- const diagnostics = analyzeSpec(spec);
271
- const generation = spec.generation;
272
- const skipped = generation?.skipped ?? [];
273
- const externalExports = spec.exports.filter((e) => e.kind === "external");
274
- const byReason = {};
275
- for (const skip of skipped) {
276
- byReason[skip.reason] = (byReason[skip.reason] ?? 0) + 1;
268
+ const pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
269
+ if (pkg.packageManager) {
270
+ if (pkg.packageManager.startsWith("bun"))
271
+ return "bun";
272
+ if (pkg.packageManager.startsWith("pnpm"))
273
+ return "pnpm";
274
+ if (pkg.packageManager.startsWith("yarn"))
275
+ return "yarn";
276
+ if (pkg.packageManager.startsWith("npm"))
277
+ return "npm";
277
278
  }
278
- const result = {
279
- summary: {
280
- total: diagnostics.missingDescriptions.length + diagnostics.deprecatedNoReason.length + diagnostics.missingParamDocs.length,
281
- missingDescriptions: diagnostics.missingDescriptions.length,
282
- deprecatedNoReason: diagnostics.deprecatedNoReason.length,
283
- missingParamDocs: diagnostics.missingParamDocs.length,
284
- ...skipped.length > 0 && { skippedExports: skipped.length },
285
- ...externalExports.length > 0 && { externalExports: externalExports.length }
286
- },
287
- diagnostics,
288
- ...skipped.length > 0 && {
289
- skippedExports: {
290
- total: skipped.length,
291
- byReason,
292
- ...options.verbose && { details: skipped }
293
- }
294
- },
295
- ...externalExports.length > 0 && {
296
- externalExports: {
297
- count: externalExports.length,
298
- ...options.verbose && {
299
- details: externalExports.map((e) => ({
300
- name: e.name,
301
- package: e.source?.package
302
- }))
303
- }
304
- }
305
- }
306
- };
307
- console.log(JSON.stringify(result, null, 2));
308
- process.exit(0);
309
- } catch (err) {
310
- const error = err instanceof Error ? err : new Error(String(err));
311
- console.log(JSON.stringify({ error: error.message }, null, 2));
312
- process.exit(0);
279
+ } catch {}
280
+ }
281
+ if (fs4.existsSync(path4.join(cwd, "bun.lockb")) || fs4.existsSync(path4.join(cwd, "bun.lock"))) {
282
+ return "bun";
283
+ }
284
+ if (fs4.existsSync(path4.join(cwd, "pnpm-lock.yaml"))) {
285
+ return "pnpm";
286
+ }
287
+ if (fs4.existsSync(path4.join(cwd, "yarn.lock"))) {
288
+ return "yarn";
289
+ }
290
+ return "npm";
291
+ }
292
+ function getDlxCommand(pkg, pm) {
293
+ const packageManager = pm || detectPackageManager();
294
+ switch (packageManager) {
295
+ case "bun":
296
+ return { cmd: "bunx", args: [pkg] };
297
+ case "pnpm":
298
+ return { cmd: "pnpm", args: ["dlx", pkg] };
299
+ case "yarn":
300
+ return { cmd: "yarn", args: ["dlx", pkg] };
301
+ default:
302
+ return { cmd: "npx", args: [pkg] };
303
+ }
304
+ }
305
+ function getShadcnCommand(subcommand, args = []) {
306
+ const dlx = getDlxCommand("shadcn@latest");
307
+ return {
308
+ cmd: dlx.cmd,
309
+ args: [...dlx.args, subcommand, ...args]
310
+ };
311
+ }
312
+
313
+ // src/commands/docs/add.ts
314
+ function createAddCommand() {
315
+ return new Command4("add").description("Install @openpkg components (wrapper for shadcn add)").argument("<components...>", "Components to install").option("-o, --overwrite", "Overwrite existing files").option("-c, --cwd <path>", "Working directory").option("-y, --yes", "Skip confirmation prompt").option("-s, --silent", "Mute output").action(async (components, options) => {
316
+ const prefixedComponents = components.map((c) => c.startsWith("@") ? c : `@openpkg/${c}`);
317
+ const extraArgs = [];
318
+ if (options.overwrite)
319
+ extraArgs.push("--overwrite");
320
+ if (options.cwd)
321
+ extraArgs.push("--cwd", options.cwd);
322
+ if (options.yes)
323
+ extraArgs.push("--yes");
324
+ if (options.silent)
325
+ extraArgs.push("--silent");
326
+ const { cmd, args } = getShadcnCommand("add", [...prefixedComponents, ...extraArgs]);
327
+ if (!options.silent) {
328
+ console.log(`Running: ${cmd} ${args.join(" ")}`);
329
+ console.log("");
313
330
  }
331
+ const child = spawn(cmd, args, {
332
+ stdio: "inherit",
333
+ shell: true
334
+ });
335
+ child.on("close", (code) => {
336
+ process.exit(code || 0);
337
+ });
314
338
  });
315
339
  }
316
340
 
317
- // src/commands/docs.ts
341
+ // src/commands/docs/generate.ts
318
342
  import * as fs5 from "node:fs";
319
343
  import * as path5 from "node:path";
320
- import { createDocs, loadSpec as loadSpec4 } from "@openpkg-ts/sdk";
344
+ import { loadSpec as loadSpec4, query, toReact } from "@openpkg-ts/sdk";
321
345
  import { Command as Command5 } from "commander";
322
346
  async function readStdin() {
323
347
  const chunks = [];
@@ -332,10 +356,32 @@ function getExtension(format) {
332
356
  return ".json";
333
357
  case "html":
334
358
  return ".html";
359
+ case "react":
360
+ return ".tsx";
335
361
  default:
336
362
  return ".md";
337
363
  }
338
364
  }
365
+ function applyFilters(spec, options) {
366
+ let qb = query(spec);
367
+ if (options.kind) {
368
+ const kinds = options.kind.split(",").map((k) => k.trim());
369
+ qb = qb.byKind(...kinds);
370
+ }
371
+ if (options.tag) {
372
+ const tags = options.tag.split(",").map((t) => t.trim());
373
+ qb = qb.byTag(...tags);
374
+ }
375
+ if (options.search) {
376
+ qb = qb.search(options.search);
377
+ }
378
+ if (options.deprecated === true) {
379
+ qb = qb.deprecated(true);
380
+ } else if (options.deprecated === false) {
381
+ qb = qb.deprecated(false);
382
+ }
383
+ return qb.toSpec();
384
+ }
339
385
  function renderExport(docs, exportId, format, collapseUnionThreshold) {
340
386
  const exp = docs.getExport(exportId);
341
387
  if (!exp)
@@ -364,8 +410,8 @@ function renderFull(docs, format, collapseUnionThreshold) {
364
410
  return docs.toMarkdown({ frontmatter: true, codeSignatures: true, collapseUnionThreshold });
365
411
  }
366
412
  }
367
- function createDocsCommand() {
368
- 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)").option("--collapse-unions <n>", "Collapse unions with more than N members (default: no collapse)").action(async (specPath, options) => {
413
+ function createGenerateCommand() {
414
+ 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) => {
369
415
  const format = options.format || "md";
370
416
  try {
371
417
  if (options.adapter && options.adapter !== "raw") {
@@ -387,36 +433,56 @@ function createDocsCommand() {
387
433
  console.error(JSON.stringify({ error: "--adapter requires --output <directory>" }));
388
434
  process.exit(1);
389
435
  }
390
- let spec;
436
+ let spec2;
391
437
  if (specPath === "-") {
392
438
  const input = await readStdin();
393
- spec = JSON.parse(input);
439
+ spec2 = JSON.parse(input);
394
440
  } else {
395
441
  const specFile = path5.resolve(specPath);
396
442
  if (!fs5.existsSync(specFile)) {
397
443
  console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
398
444
  process.exit(1);
399
445
  }
400
- spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
446
+ spec2 = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
401
447
  }
402
- await adapter.generate(spec, path5.resolve(options.output));
448
+ spec2 = applyFilters(spec2, options);
449
+ await adapter.generate(spec2, path5.resolve(options.output));
403
450
  console.error(`Generated docs with ${options.adapter} adapter to ${options.output}`);
404
451
  return;
405
452
  }
406
- let docs;
453
+ let spec;
407
454
  if (specPath === "-") {
408
455
  const input = await readStdin();
409
- const spec = JSON.parse(input);
410
- docs = loadSpec4(spec);
456
+ spec = JSON.parse(input);
411
457
  } else {
412
458
  const specFile = path5.resolve(specPath);
413
459
  if (!fs5.existsSync(specFile)) {
414
460
  console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
415
461
  process.exit(1);
416
462
  }
417
- docs = createDocs(specFile);
463
+ spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
418
464
  }
465
+ spec = applyFilters(spec, options);
466
+ const docs = loadSpec4(spec);
419
467
  const collapseUnionThreshold = options.collapseUnions ? parseInt(options.collapseUnions, 10) : undefined;
468
+ if (format === "react") {
469
+ if (!options.output) {
470
+ console.error(JSON.stringify({ error: "--format react requires --output <directory>" }));
471
+ process.exit(1);
472
+ }
473
+ const variant = options.variant === "index" ? "index" : "full";
474
+ await toReact(spec, {
475
+ outDir: path5.resolve(options.output),
476
+ variant,
477
+ componentsPath: options.componentsPath ?? "@/components/api"
478
+ });
479
+ console.error(`Generated React layout to ${options.output}`);
480
+ console.error(` - page.tsx: Layout file`);
481
+ console.error(` - openpkg.json: Spec data`);
482
+ console.error(`
483
+ Next: Add components with 'openpkg docs add function-section'`);
484
+ return;
485
+ }
420
486
  if (options.export) {
421
487
  const exports = docs.getAllExports();
422
488
  const exp = exports.find((e) => e.name === options.export);
@@ -469,108 +535,115 @@ function createDocsCommand() {
469
535
  });
470
536
  }
471
537
 
472
- // src/commands/filter.ts
538
+ // src/commands/docs/init.ts
473
539
  import * as fs6 from "node:fs";
474
540
  import * as path6 from "node:path";
475
- import { filterSpec } from "@openpkg-ts/sdk";
476
541
  import { Command as Command6 } from "commander";
477
- var VALID_KINDS = [
478
- "function",
479
- "class",
480
- "variable",
481
- "interface",
482
- "type",
483
- "enum",
484
- "module",
485
- "namespace",
486
- "reference",
487
- "external"
488
- ];
489
- function loadSpec5(filePath) {
490
- const resolved = path6.resolve(filePath);
491
- const content = fs6.readFileSync(resolved, "utf-8");
492
- return JSON.parse(content);
542
+ var COMPONENTS_JSON = "components.json";
543
+ var REGISTRY_URL = "https://raw.githubusercontent.com/anthropics/openpkg-ts/main/registry/r/{name}.json";
544
+ function loadComponentsJson() {
545
+ const configPath = path6.resolve(COMPONENTS_JSON);
546
+ if (!fs6.existsSync(configPath))
547
+ return null;
548
+ return JSON.parse(fs6.readFileSync(configPath, "utf-8"));
493
549
  }
494
- function parseList(val) {
495
- if (!val)
496
- return;
497
- return val.split(",").map((s) => s.trim()).filter(Boolean);
498
- }
499
- function validateKinds(kinds) {
500
- const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
501
- if (invalid.length > 0) {
502
- throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS.join(", ")}`);
503
- }
504
- return kinds;
505
- }
506
- function createFilterCommand() {
507
- 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("--search-members", "Also search member names/descriptions").option("--search-docs", "Also search param/return descriptions and examples").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) => {
508
- try {
509
- const spec = loadSpec5(specPath);
510
- const criteria = {};
511
- if (options.kind) {
512
- const kinds = parseList(options.kind);
513
- if (kinds)
514
- criteria.kinds = validateKinds(kinds);
515
- }
516
- if (options.name)
517
- criteria.names = parseList(options.name);
518
- if (options.id)
519
- criteria.ids = parseList(options.id);
520
- if (options.tag)
521
- criteria.tags = parseList(options.tag);
522
- if (options.deprecated !== undefined)
523
- criteria.deprecated = options.deprecated;
524
- if (options.hasDescription)
525
- criteria.hasDescription = true;
526
- if (options.missingDescription)
527
- criteria.hasDescription = false;
528
- if (options.search)
529
- criteria.search = options.search;
530
- if (options.searchMembers)
531
- criteria.searchMembers = true;
532
- if (options.searchDocs)
533
- criteria.searchDocs = true;
534
- if (options.module)
535
- criteria.module = options.module;
536
- const result = filterSpec(spec, criteria);
537
- let output;
538
- if (options.summary) {
539
- output = { matched: result.matched, total: result.total };
540
- } else if (options.quiet) {
541
- output = result.spec;
542
- } else {
543
- output = { spec: result.spec, matched: result.matched, total: result.total };
544
- }
545
- const json = JSON.stringify(output, null, 2);
546
- if (options.output) {
547
- fs6.writeFileSync(path6.resolve(options.output), json);
548
- } else {
549
- console.log(json);
550
- }
551
- } catch (err) {
552
- const error = err instanceof Error ? err : new Error(String(err));
553
- console.error(JSON.stringify({ error: error.message }, null, 2));
550
+ function createInitCommand() {
551
+ 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) => {
552
+ const configPath = path6.resolve(COMPONENTS_JSON);
553
+ const registryUrl = options.registry || REGISTRY_URL;
554
+ if (!fs6.existsSync(configPath)) {
555
+ console.error(`${COMPONENTS_JSON} not found.`);
556
+ console.error('Run "npx shadcn@latest init" first to initialize shadcn.');
557
+ process.exit(1);
558
+ }
559
+ const config = loadComponentsJson();
560
+ if (!config) {
561
+ console.error(`Failed to parse ${COMPONENTS_JSON}`);
554
562
  process.exit(1);
555
563
  }
564
+ config.registries = config.registries || {};
565
+ config.registries["@openpkg"] = registryUrl;
566
+ fs6.writeFileSync(configPath, JSON.stringify(config, null, 2));
567
+ console.log(`Added @openpkg registry to ${COMPONENTS_JSON}`);
568
+ console.log("");
569
+ console.log("Usage:");
570
+ console.log(" npx shadcn@latest add @openpkg/function-section");
571
+ console.log(" npx shadcn@latest add @openpkg/export-card");
572
+ console.log("");
573
+ console.log("List components:");
574
+ console.log(" openpkg docs list");
556
575
  });
557
576
  }
558
577
 
578
+ // src/commands/docs/list.ts
579
+ import { spawn as spawn2 } from "node:child_process";
580
+ import { Command as Command7 } from "commander";
581
+ function createListCommand() {
582
+ return new Command7("list").description("List @openpkg components (wrapper for shadcn list)").option("-q, --query <query>", "Search query").option("-l, --limit <number>", "Max items to display").option("-c, --cwd <cwd>", "Working directory").action(async (options) => {
583
+ const extraArgs = ["@openpkg"];
584
+ if (options.query)
585
+ extraArgs.push("-q", options.query);
586
+ if (options.limit)
587
+ extraArgs.push("-l", options.limit);
588
+ if (options.cwd)
589
+ extraArgs.push("-c", options.cwd);
590
+ const { cmd, args } = getShadcnCommand("list", extraArgs);
591
+ const child = spawn2(cmd, args, {
592
+ stdio: "inherit",
593
+ shell: true
594
+ });
595
+ child.on("close", (code) => {
596
+ process.exit(code || 0);
597
+ });
598
+ });
599
+ }
600
+
601
+ // src/commands/docs/view.ts
602
+ import { spawn as spawn3 } from "node:child_process";
603
+ import { Command as Command8 } from "commander";
604
+ function createViewCommand() {
605
+ return new Command8("view").description("View @openpkg component before installing (wrapper for shadcn view)").argument("<components...>", "Components to view").option("-c, --cwd <cwd>", "Working directory").action(async (components, options) => {
606
+ const prefixedComponents = components.map((c) => c.startsWith("@") ? c : `@openpkg/${c}`);
607
+ const extraArgs = [...prefixedComponents];
608
+ if (options.cwd)
609
+ extraArgs.push("-c", options.cwd);
610
+ const { cmd, args } = getShadcnCommand("view", extraArgs);
611
+ const child = spawn3(cmd, args, {
612
+ stdio: "inherit",
613
+ shell: true
614
+ });
615
+ child.on("close", (code) => {
616
+ process.exit(code || 0);
617
+ });
618
+ });
619
+ }
620
+
621
+ // src/commands/docs/index.ts
622
+ function createDocsCommand() {
623
+ const docs = new Command9("docs").description("Documentation generation and component registry");
624
+ docs.addCommand(createGenerateCommand());
625
+ docs.addCommand(createInitCommand());
626
+ docs.addCommand(createListCommand());
627
+ docs.addCommand(createViewCommand());
628
+ docs.addCommand(createAddCommand());
629
+ return docs;
630
+ }
631
+
559
632
  // src/commands/semver.ts
560
633
  import * as fs7 from "node:fs";
561
634
  import * as path7 from "node:path";
562
635
  import { diffSpec as diffSpec3, recommendSemverBump as recommendSemverBump2 } from "@openpkg-ts/spec";
563
- import { Command as Command7 } from "commander";
564
- function loadSpec6(filePath) {
636
+ import { Command as Command10 } from "commander";
637
+ function loadSpec5(filePath) {
565
638
  const resolved = path7.resolve(filePath);
566
639
  const content = fs7.readFileSync(resolved, "utf-8");
567
640
  return JSON.parse(content);
568
641
  }
569
642
  function createSemverCommand() {
570
- 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) => {
643
+ 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) => {
571
644
  try {
572
- const oldSpec = loadSpec6(oldPath);
573
- const newSpec = loadSpec6(newPath);
645
+ const oldSpec = loadSpec5(oldPath);
646
+ const newSpec = loadSpec5(newPath);
574
647
  const diff = diffSpec3(oldSpec, newSpec);
575
648
  const recommendation = recommendSemverBump2(diff);
576
649
  const result = {
@@ -586,19 +659,48 @@ function createSemverCommand() {
586
659
  });
587
660
  }
588
661
 
589
- // src/commands/snapshot.ts
662
+ // src/commands/spec.ts
590
663
  import * as fs8 from "node:fs";
591
664
  import * as path8 from "node:path";
592
665
  import {
666
+ analyzeSpec,
593
667
  extractSpec,
668
+ filterSpec,
669
+ getExport,
670
+ listExports,
594
671
  loadConfig,
595
672
  mergeConfig
596
673
  } from "@openpkg-ts/sdk";
597
- import { Command as Command8 } from "commander";
598
- function parseFilter(value) {
599
- if (!value)
674
+ import { getValidationErrors } from "@openpkg-ts/spec";
675
+ import { Command as Command11 } from "commander";
676
+ var VALID_KINDS = [
677
+ "function",
678
+ "class",
679
+ "variable",
680
+ "interface",
681
+ "type",
682
+ "enum",
683
+ "module",
684
+ "namespace",
685
+ "reference",
686
+ "external"
687
+ ];
688
+ function loadSpec6(filePath) {
689
+ const resolved = path8.resolve(filePath);
690
+ const content = fs8.readFileSync(resolved, "utf-8");
691
+ return JSON.parse(content);
692
+ }
693
+ function parseList(val) {
694
+ if (!val)
600
695
  return;
601
- return value.split(",").map((s) => s.trim()).filter(Boolean);
696
+ return val.split(",").map((s) => s.trim()).filter(Boolean);
697
+ }
698
+ function validateKinds(kinds) {
699
+ const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
700
+ if (invalid.length > 0) {
701
+ throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS.join(", ")}`);
702
+ }
703
+ return kinds;
602
704
  }
603
705
  function formatDiagnostics(diagnostics) {
604
706
  return diagnostics.map((d) => ({
@@ -609,11 +711,8 @@ function formatDiagnostics(diagnostics) {
609
711
  ...d.location && { location: d.location }
610
712
  }));
611
713
  }
612
- function createSnapshotCommand() {
613
- return new Command8("snapshot").description(`Generate full OpenPkg spec from TypeScript entry point
614
-
615
- ` + `Config: Reads from openpkg.config.json or package.json "openpkg" field.
616
- ` + "CLI flags override config file settings.").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").option("--verbose", "Show detailed output including skipped exports").option("--include-private", "Include private/protected class members").option("--external-include <patterns...>", "Resolve re-exports from these packages (globs supported)").option("--external-exclude <patterns...>", "Never resolve from these packages").option("--external-depth <n>", "Max transitive depth for external resolution (default: 1)", "1").action(async (entry, options) => {
714
+ 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) => {
617
716
  const entryFile = path8.resolve(entry);
618
717
  const entryDir = path8.dirname(entryFile);
619
718
  const fileConfig = loadConfig(entryDir);
@@ -630,8 +729,8 @@ function createSnapshotCommand() {
630
729
  maxTypeDepth: parseInt(options.maxDepth ?? "4", 10),
631
730
  resolveExternalTypes: !options.skipResolve,
632
731
  schemaExtraction: options.runtime ? "hybrid" : "static",
633
- only: parseFilter(options.only),
634
- ignore: parseFilter(options.ignore),
732
+ only: parseList(options.only),
733
+ ignore: parseList(options.ignore),
635
734
  includePrivate: options.includePrivate,
636
735
  ...mergedConfig.externals && { externals: mergedConfig.externals }
637
736
  };
@@ -653,58 +752,15 @@ function createSnapshotCommand() {
653
752
  }
654
753
  }
655
754
  },
656
- ...externalExports.length > 0 && {
657
- external: {
658
- count: externalExports.length,
659
- ...options.verbose && {
660
- exports: externalExports.map((e) => ({
661
- name: e.name,
662
- package: e.source?.package
663
- }))
664
- }
665
- }
666
- },
667
- ...result.runtimeSchemas && {
668
- runtime: {
669
- extracted: result.runtimeSchemas.extracted,
670
- merged: result.runtimeSchemas.merged,
671
- vendors: result.runtimeSchemas.vendors
672
- }
673
- }
755
+ ...externalExports.length > 0 && { external: { count: externalExports.length } }
674
756
  };
675
757
  console.error(JSON.stringify(summary, null, 2));
676
- if (externalExports.length > 0 || (result.verification?.skipped ?? 0) > 0) {
677
- console.error("");
678
- if (externalExports.length > 0) {
679
- if (options.verbose) {
680
- console.error(`⚠ ${externalExports.length} external re-export(s) (install dependencies for full type info):`);
681
- for (const exp of externalExports) {
682
- console.error(` - ${exp.name} from "${exp.source?.package}"`);
683
- }
684
- } else {
685
- console.error(`⚠ ${externalExports.length} external re-export(s) (install dependencies for full type info)`);
686
- }
687
- }
688
- const skipped = result.verification?.details.skipped ?? [];
689
- if (skipped.length > 0) {
690
- if (options.verbose) {
691
- console.error(`⚠ ${skipped.length} export(s) skipped:`);
692
- for (const skip of skipped) {
693
- const pkgInfo = skip.package ? ` from "${skip.package}"` : "";
694
- console.error(` - ${skip.name} (${skip.reason})${pkgInfo}`);
695
- }
696
- } else {
697
- console.error(`⚠ ${skipped.length} export(s) skipped (use --verbose for details)`);
698
- }
699
- }
700
- }
701
758
  if (options.verify && result.verification && result.verification.failed > 0) {
702
- const errorOutput = {
759
+ console.error(JSON.stringify({
703
760
  error: "Export verification failed",
704
761
  failed: result.verification.details.failed,
705
762
  diagnostics: formatDiagnostics(result.diagnostics)
706
- };
707
- console.error(JSON.stringify(errorOutput, null, 2));
763
+ }, null, 2));
708
764
  process.exit(1);
709
765
  }
710
766
  const specJson = JSON.stringify(result.spec, null, 2);
@@ -717,40 +773,118 @@ function createSnapshotCommand() {
717
773
  }
718
774
  } catch (err) {
719
775
  const error = err instanceof Error ? err : new Error(String(err));
720
- const errorOutput = {
721
- error: error.message,
722
- ...error.stack && { stack: error.stack }
723
- };
724
- console.error(JSON.stringify(errorOutput, null, 2));
776
+ console.error(JSON.stringify({ error: error.message }, null, 2));
725
777
  process.exit(1);
726
778
  }
727
779
  });
728
780
  }
729
-
730
- // src/commands/validate.ts
731
- import * as fs9 from "node:fs";
732
- import * as path9 from "node:path";
733
- import { getValidationErrors } from "@openpkg-ts/spec";
734
- import { Command as Command9 } from "commander";
735
- function loadJSON2(filePath) {
736
- const resolved = path9.resolve(filePath);
737
- const content = fs9.readFileSync(resolved, "utf-8");
738
- return JSON.parse(content);
739
- }
740
- function createValidateCommand() {
741
- 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) => {
781
+ function createValidateSubcommand() {
782
+ 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) => {
742
783
  try {
743
- const spec = loadJSON2(specPath);
784
+ const spec = loadSpec6(specPath);
744
785
  const version = options.version ?? "latest";
745
786
  const errors = getValidationErrors(spec, version);
787
+ console.log(JSON.stringify({ valid: errors.length === 0, errors }, null, 2));
788
+ if (errors.length > 0)
789
+ process.exit(1);
790
+ } catch (err) {
791
+ const error = err instanceof Error ? err : new Error(String(err));
792
+ console.error(JSON.stringify({ error: error.message }, null, 2));
793
+ process.exit(1);
794
+ }
795
+ });
796
+ }
797
+ function createFilterSubcommand() {
798
+ 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) => {
799
+ try {
800
+ const spec = loadSpec6(specPath);
801
+ const criteria = {};
802
+ if (options.kind) {
803
+ const kinds = parseList(options.kind);
804
+ if (kinds)
805
+ criteria.kinds = validateKinds(kinds);
806
+ }
807
+ if (options.name)
808
+ criteria.names = parseList(options.name);
809
+ if (options.id)
810
+ criteria.ids = parseList(options.id);
811
+ if (options.tag)
812
+ criteria.tags = parseList(options.tag);
813
+ if (options.deprecated !== undefined)
814
+ criteria.deprecated = options.deprecated;
815
+ if (options.hasDescription)
816
+ criteria.hasDescription = true;
817
+ if (options.missingDescription)
818
+ criteria.hasDescription = false;
819
+ if (options.search)
820
+ criteria.search = options.search;
821
+ if (options.searchMembers)
822
+ criteria.searchMembers = true;
823
+ if (options.searchDocs)
824
+ criteria.searchDocs = true;
825
+ if (options.module)
826
+ criteria.module = options.module;
827
+ const result = filterSpec(spec, criteria);
828
+ let output;
829
+ if (options.summary) {
830
+ output = { matched: result.matched, total: result.total };
831
+ } else if (options.quiet) {
832
+ output = result.spec;
833
+ } else {
834
+ output = { spec: result.spec, matched: result.matched, total: result.total };
835
+ }
836
+ const json = JSON.stringify(output, null, 2);
837
+ if (options.output) {
838
+ fs8.writeFileSync(path8.resolve(options.output), json);
839
+ } else {
840
+ console.log(json);
841
+ }
842
+ } catch (err) {
843
+ const error = err instanceof Error ? err : new Error(String(err));
844
+ console.error(JSON.stringify({ error: error.message }, null, 2));
845
+ process.exit(1);
846
+ }
847
+ });
848
+ }
849
+ function createLintSubcommand() {
850
+ 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) => {
851
+ try {
852
+ const spec = loadSpec6(specPath);
853
+ const diagnostics = analyzeSpec(spec);
854
+ const generation = spec.generation;
855
+ const skipped = generation?.skipped ?? [];
856
+ const externalExports = spec.exports.filter((e) => e.kind === "external");
857
+ const byReason = {};
858
+ for (const skip of skipped) {
859
+ byReason[skip.reason] = (byReason[skip.reason] ?? 0) + 1;
860
+ }
746
861
  const result = {
747
- valid: errors.length === 0,
748
- errors
862
+ summary: {
863
+ total: diagnostics.missingDescriptions.length + diagnostics.deprecatedNoReason.length + diagnostics.missingParamDocs.length,
864
+ missingDescriptions: diagnostics.missingDescriptions.length,
865
+ deprecatedNoReason: diagnostics.deprecatedNoReason.length,
866
+ missingParamDocs: diagnostics.missingParamDocs.length,
867
+ ...skipped.length > 0 && { skippedExports: skipped.length },
868
+ ...externalExports.length > 0 && { externalExports: externalExports.length }
869
+ },
870
+ diagnostics,
871
+ ...skipped.length > 0 && {
872
+ skippedExports: {
873
+ total: skipped.length,
874
+ byReason,
875
+ ...options.verbose && { details: skipped }
876
+ }
877
+ },
878
+ ...externalExports.length > 0 && {
879
+ externalExports: {
880
+ count: externalExports.length,
881
+ ...options.verbose && {
882
+ details: externalExports.map((e) => ({ name: e.name, package: e.source?.package }))
883
+ }
884
+ }
885
+ }
749
886
  };
750
887
  console.log(JSON.stringify(result, null, 2));
751
- if (errors.length > 0) {
752
- process.exit(1);
753
- }
754
888
  } catch (err) {
755
889
  const error = err instanceof Error ? err : new Error(String(err));
756
890
  console.error(JSON.stringify({ error: error.message }, null, 2));
@@ -758,40 +892,82 @@ function createValidateCommand() {
758
892
  }
759
893
  });
760
894
  }
895
+ function createListSubcommand() {
896
+ return new Command11("list").description("List exports from a TypeScript entry point").argument("<entry>", "Entry point file path").action(async (entry) => {
897
+ const entryFile = path8.resolve(entry);
898
+ const result = await listExports({ entryFile });
899
+ if (result.errors.length > 0) {
900
+ console.error(JSON.stringify({ errors: result.errors }, null, 2));
901
+ process.exit(1);
902
+ }
903
+ console.log(JSON.stringify(result.exports, null, 2));
904
+ });
905
+ }
906
+ function createGetSubcommand() {
907
+ 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) => {
908
+ const entryFile = path8.resolve(entry);
909
+ const result = await getExport({ entryFile, exportName: name });
910
+ if (!result.export) {
911
+ const errorMsg = result.errors.length > 0 ? result.errors.join("; ") : `Export '${name}' not found`;
912
+ console.error(JSON.stringify({ error: errorMsg }, null, 2));
913
+ process.exit(1);
914
+ }
915
+ const output = { export: result.export };
916
+ if (result.types.length > 0) {
917
+ output.types = result.types;
918
+ }
919
+ console.log(JSON.stringify(output, null, 2));
920
+ });
921
+ }
922
+ function createSpecCommand() {
923
+ const spec = new Command11("spec").description("Spec extraction and manipulation commands");
924
+ spec.addCommand(createSnapshotSubcommand());
925
+ spec.addCommand(createValidateSubcommand());
926
+ spec.addCommand(createFilterSubcommand());
927
+ spec.addCommand(createLintSubcommand());
928
+ spec.addCommand(createListSubcommand());
929
+ spec.addCommand(createGetSubcommand());
930
+ return spec;
931
+ }
761
932
 
762
933
  // bin/openpkg.ts
763
- var program = new Command10;
934
+ var program = new Command12;
764
935
  program.name("openpkg").description("OpenPkg CLI - TypeScript API extraction primitives").version(package_default.version);
765
- program.command("list").description("List exports from a TypeScript entry point").argument("<entry>", "Entry point file path").action(async (entry) => {
766
- const entryFile = path10.resolve(entry);
767
- const result = await listExports({ entryFile });
768
- if (result.errors.length > 0) {
769
- console.error(JSON.stringify({ errors: result.errors }, null, 2));
770
- process.exit(1);
771
- }
772
- console.log(JSON.stringify(result.exports, null, 2));
773
- });
774
- 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) => {
775
- const entryFile = path10.resolve(entry);
776
- const result = await getExport({ entryFile, exportName: name });
777
- if (!result.export) {
778
- const errorMsg = result.errors.length > 0 ? result.errors.join("; ") : `Export '${name}' not found`;
779
- console.error(JSON.stringify({ error: errorMsg }, null, 2));
780
- process.exit(1);
781
- }
782
- const output = { export: result.export };
783
- if (result.types.length > 0) {
784
- output.types = result.types;
785
- }
786
- console.log(JSON.stringify(output, null, 2));
787
- });
788
- program.addCommand(createSnapshotCommand());
789
- program.addCommand(createDiffCommand());
936
+ program.addCommand(createSpecCommand());
790
937
  program.addCommand(createDocsCommand());
938
+ program.addCommand(createDiffCommand());
791
939
  program.addCommand(createBreakingCommand());
792
940
  program.addCommand(createChangelogCommand());
793
941
  program.addCommand(createSemverCommand());
794
- program.addCommand(createValidateCommand());
795
- program.addCommand(createDiagnosticsCommand());
796
- program.addCommand(createFilterCommand());
942
+ 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);
797
973
  program.parse();
@@ -1,12 +1,3 @@
1
1
  import { getExport, listExports } from "@openpkg-ts/sdk";
2
- import { OpenPkg } from "@openpkg-ts/spec";
3
- type FilterResult = {
4
- spec: OpenPkg;
5
- matched: number;
6
- total: number;
7
- };
8
- type FilterSummaryResult = {
9
- matched: number;
10
- total: number;
11
- };
2
+ import { FilterResult, FilterSummaryResult } from "./commands/filter";
12
3
  export { listExports, getExport, FilterSummaryResult, FilterResult };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/cli",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
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.2",
27
- "@openpkg-ts/sdk": "^0.33.1",
26
+ "@openpkg-ts/adapters": "^0.3.3",
27
+ "@openpkg-ts/sdk": "^0.34.0",
28
28
  "commander": "^14.0.0"
29
29
  },
30
30
  "devDependencies": {