@openpkg-ts/cli 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.1",
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,14 +32,14 @@ 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.4",
36
+ "@openpkg-ts/sdk": "^0.34.1",
39
37
  commander: "^14.0.0"
40
38
  },
41
39
  devDependencies: {
42
40
  "@types/bun": "latest",
43
41
  "@types/node": "^20.0.0",
44
- bunup: "latest"
42
+ bunup: "^0.16.20"
45
43
  },
46
44
  publishConfig: {
47
45
  access: "public"
@@ -253,78 +251,108 @@ 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
- const chunks = [];
324
- for await (const chunk of process.stdin) {
325
- 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)}`);
326
355
  }
327
- return Buffer.concat(chunks).toString("utf-8");
328
356
  }
329
357
  function getExtension(format) {
330
358
  switch (format) {
@@ -332,10 +360,32 @@ function getExtension(format) {
332
360
  return ".json";
333
361
  case "html":
334
362
  return ".html";
363
+ case "react":
364
+ return ".tsx";
335
365
  default:
336
366
  return ".md";
337
367
  }
338
368
  }
369
+ function applyFilters(spec, options) {
370
+ let qb = query(spec);
371
+ if (options.kind) {
372
+ const kinds = options.kind.split(",").map((k) => k.trim());
373
+ qb = qb.byKind(...kinds);
374
+ }
375
+ if (options.tag) {
376
+ const tags = options.tag.split(",").map((t) => t.trim());
377
+ qb = qb.byTag(...tags);
378
+ }
379
+ if (options.search) {
380
+ qb = qb.search(options.search);
381
+ }
382
+ if (options.deprecated === true) {
383
+ qb = qb.deprecated(true);
384
+ } else if (options.deprecated === false) {
385
+ qb = qb.deprecated(false);
386
+ }
387
+ return qb.toSpec();
388
+ }
339
389
  function renderExport(docs, exportId, format, collapseUnionThreshold) {
340
390
  const exp = docs.getExport(exportId);
341
391
  if (!exp)
@@ -364,8 +414,8 @@ function renderFull(docs, format, collapseUnionThreshold) {
364
414
  return docs.toMarkdown({ frontmatter: true, codeSignatures: true, collapseUnionThreshold });
365
415
  }
366
416
  }
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) => {
417
+ function createGenerateCommand() {
418
+ 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
419
  const format = options.format || "md";
370
420
  try {
371
421
  if (options.adapter && options.adapter !== "raw") {
@@ -387,36 +437,56 @@ function createDocsCommand() {
387
437
  console.error(JSON.stringify({ error: "--adapter requires --output <directory>" }));
388
438
  process.exit(1);
389
439
  }
390
- let spec;
440
+ let spec2;
391
441
  if (specPath === "-") {
392
442
  const input = await readStdin();
393
- spec = JSON.parse(input);
443
+ spec2 = JSON.parse(input);
394
444
  } else {
395
445
  const specFile = path5.resolve(specPath);
396
446
  if (!fs5.existsSync(specFile)) {
397
447
  console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
398
448
  process.exit(1);
399
449
  }
400
- spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
450
+ spec2 = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
401
451
  }
402
- await adapter.generate(spec, path5.resolve(options.output));
452
+ spec2 = applyFilters(spec2, options);
453
+ await adapter.generate(spec2, path5.resolve(options.output));
403
454
  console.error(`Generated docs with ${options.adapter} adapter to ${options.output}`);
404
455
  return;
405
456
  }
406
- let docs;
457
+ let spec;
407
458
  if (specPath === "-") {
408
459
  const input = await readStdin();
409
- const spec = JSON.parse(input);
410
- docs = loadSpec4(spec);
460
+ spec = JSON.parse(input);
411
461
  } else {
412
462
  const specFile = path5.resolve(specPath);
413
463
  if (!fs5.existsSync(specFile)) {
414
464
  console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
415
465
  process.exit(1);
416
466
  }
417
- docs = createDocs(specFile);
467
+ spec = JSON.parse(fs5.readFileSync(specFile, "utf-8"));
418
468
  }
469
+ spec = applyFilters(spec, options);
470
+ const docs = loadSpec4(spec);
419
471
  const collapseUnionThreshold = options.collapseUnions ? parseInt(options.collapseUnions, 10) : undefined;
472
+ if (format === "react") {
473
+ if (!options.output) {
474
+ console.error(JSON.stringify({ error: "--format react requires --output <directory>" }));
475
+ process.exit(1);
476
+ }
477
+ const variant = options.variant === "index" ? "index" : "full";
478
+ await toReact(spec, {
479
+ outDir: path5.resolve(options.output),
480
+ variant,
481
+ componentsPath: options.componentsPath ?? "@/components/api"
482
+ });
483
+ console.error(`Generated React layout to ${options.output}`);
484
+ console.error(` - page.tsx: Layout file`);
485
+ console.error(` - openpkg.json: Spec data`);
486
+ console.error(`
487
+ Next: Add components with 'openpkg docs add function-section'`);
488
+ return;
489
+ }
420
490
  if (options.export) {
421
491
  const exports = docs.getAllExports();
422
492
  const exp = exports.find((e) => e.name === options.export);
@@ -445,8 +515,14 @@ function createDocsCommand() {
445
515
  }
446
516
  const exports = docs.getAllExports();
447
517
  for (const exp of exports) {
448
- const filename = `${exp.name}${getExtension(format)}`;
518
+ const filename = path5.basename(`${exp.name}${getExtension(format)}`);
449
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)) {
523
+ console.error(JSON.stringify({ error: `Path traversal detected: ${exp.name}` }));
524
+ process.exit(1);
525
+ }
450
526
  const content = renderExport(docs, exp.id, format, collapseUnionThreshold);
451
527
  fs5.writeFileSync(filePath, content);
452
528
  }
@@ -469,108 +545,115 @@ function createDocsCommand() {
469
545
  });
470
546
  }
471
547
 
472
- // src/commands/filter.ts
548
+ // src/commands/docs/init.ts
473
549
  import * as fs6 from "node:fs";
474
550
  import * as path6 from "node:path";
475
- import { filterSpec } from "@openpkg-ts/sdk";
476
551
  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);
493
- }
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;
552
+ var COMPONENTS_JSON = "components.json";
553
+ var REGISTRY_URL = "https://raw.githubusercontent.com/anthropics/openpkg-ts/main/registry/r/{name}.json";
554
+ function loadComponentsJson() {
555
+ const configPath = path6.resolve(COMPONENTS_JSON);
556
+ if (!fs6.existsSync(configPath))
557
+ return null;
558
+ return JSON.parse(fs6.readFileSync(configPath, "utf-8"));
505
559
  }
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));
560
+ function createInitCommand() {
561
+ 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);
563
+ const registryUrl = options.registry || REGISTRY_URL;
564
+ if (!fs6.existsSync(configPath)) {
565
+ console.error(`${COMPONENTS_JSON} not found.`);
566
+ console.error('Run "npx shadcn@latest init" first to initialize shadcn.');
567
+ process.exit(1);
568
+ }
569
+ const config = loadComponentsJson();
570
+ if (!config) {
571
+ console.error(`Failed to parse ${COMPONENTS_JSON}`);
554
572
  process.exit(1);
555
573
  }
574
+ config.registries = config.registries || {};
575
+ config.registries["@openpkg"] = registryUrl;
576
+ fs6.writeFileSync(configPath, JSON.stringify(config, null, 2));
577
+ console.log(`Added @openpkg registry to ${COMPONENTS_JSON}`);
578
+ console.log("");
579
+ console.log("Usage:");
580
+ console.log(" npx shadcn@latest add @openpkg/function-section");
581
+ console.log(" npx shadcn@latest add @openpkg/export-card");
582
+ console.log("");
583
+ console.log("List components:");
584
+ console.log(" openpkg docs list");
585
+ });
586
+ }
587
+
588
+ // src/commands/docs/list.ts
589
+ import { spawn as spawn2 } from "node:child_process";
590
+ import { Command as Command7 } from "commander";
591
+ function createListCommand() {
592
+ 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) => {
593
+ const extraArgs = ["@openpkg"];
594
+ if (options.query)
595
+ extraArgs.push("-q", options.query);
596
+ if (options.limit)
597
+ extraArgs.push("-l", options.limit);
598
+ if (options.cwd)
599
+ extraArgs.push("-c", options.cwd);
600
+ const { cmd, args } = getShadcnCommand("list", extraArgs);
601
+ const child = spawn2(cmd, args, {
602
+ stdio: "inherit",
603
+ shell: true
604
+ });
605
+ child.on("close", (code) => {
606
+ process.exit(code || 0);
607
+ });
556
608
  });
557
609
  }
558
610
 
611
+ // src/commands/docs/view.ts
612
+ import { spawn as spawn3 } from "node:child_process";
613
+ import { Command as Command8 } from "commander";
614
+ function createViewCommand() {
615
+ 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) => {
616
+ const prefixedComponents = components.map((c) => c.startsWith("@") ? c : `@openpkg/${c}`);
617
+ const extraArgs = [...prefixedComponents];
618
+ if (options.cwd)
619
+ extraArgs.push("-c", options.cwd);
620
+ const { cmd, args } = getShadcnCommand("view", extraArgs);
621
+ const child = spawn3(cmd, args, {
622
+ stdio: "inherit",
623
+ shell: true
624
+ });
625
+ child.on("close", (code) => {
626
+ process.exit(code || 0);
627
+ });
628
+ });
629
+ }
630
+
631
+ // src/commands/docs/index.ts
632
+ function createDocsCommand() {
633
+ const docs = new Command9("docs").description("Documentation generation and component registry");
634
+ docs.addCommand(createGenerateCommand());
635
+ docs.addCommand(createInitCommand());
636
+ docs.addCommand(createListCommand());
637
+ docs.addCommand(createViewCommand());
638
+ docs.addCommand(createAddCommand());
639
+ return docs;
640
+ }
641
+
559
642
  // src/commands/semver.ts
560
643
  import * as fs7 from "node:fs";
561
644
  import * as path7 from "node:path";
562
645
  import { diffSpec as diffSpec3, recommendSemverBump as recommendSemverBump2 } from "@openpkg-ts/spec";
563
- import { Command as Command7 } from "commander";
564
- function loadSpec6(filePath) {
646
+ import { Command as Command10 } from "commander";
647
+ function loadSpec5(filePath) {
565
648
  const resolved = path7.resolve(filePath);
566
649
  const content = fs7.readFileSync(resolved, "utf-8");
567
650
  return JSON.parse(content);
568
651
  }
569
652
  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) => {
653
+ 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
654
  try {
572
- const oldSpec = loadSpec6(oldPath);
573
- const newSpec = loadSpec6(newPath);
655
+ const oldSpec = loadSpec5(oldPath);
656
+ const newSpec = loadSpec5(newPath);
574
657
  const diff = diffSpec3(oldSpec, newSpec);
575
658
  const recommendation = recommendSemverBump2(diff);
576
659
  const result = {
@@ -586,19 +669,64 @@ function createSemverCommand() {
586
669
  });
587
670
  }
588
671
 
589
- // src/commands/snapshot.ts
672
+ // src/commands/spec.ts
590
673
  import * as fs8 from "node:fs";
591
674
  import * as path8 from "node:path";
592
675
  import {
676
+ analyzeSpec,
593
677
  extractSpec,
678
+ filterSpec,
679
+ getExport,
680
+ listExports,
594
681
  loadConfig,
595
682
  mergeConfig
596
683
  } from "@openpkg-ts/sdk";
597
- import { Command as Command8 } from "commander";
598
- function parseFilter(value) {
599
- if (!value)
684
+ import { getValidationErrors } from "@openpkg-ts/spec";
685
+ import { Command as Command11 } from "commander";
686
+ var VALID_KINDS = [
687
+ "function",
688
+ "class",
689
+ "variable",
690
+ "interface",
691
+ "type",
692
+ "enum",
693
+ "module",
694
+ "namespace",
695
+ "reference",
696
+ "external"
697
+ ];
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
+ function parseList(val) {
720
+ if (!val)
600
721
  return;
601
- return value.split(",").map((s) => s.trim()).filter(Boolean);
722
+ return val.split(",").map((s) => s.trim()).filter(Boolean);
723
+ }
724
+ function validateKinds(kinds) {
725
+ const invalid = kinds.filter((k) => !VALID_KINDS.includes(k));
726
+ if (invalid.length > 0) {
727
+ throw new Error(`Invalid kind(s): ${invalid.join(", ")}. Valid kinds: ${VALID_KINDS.join(", ")}`);
728
+ }
729
+ return kinds;
602
730
  }
603
731
  function formatDiagnostics(diagnostics) {
604
732
  return diagnostics.map((d) => ({
@@ -609,11 +737,8 @@ function formatDiagnostics(diagnostics) {
609
737
  ...d.location && { location: d.location }
610
738
  }));
611
739
  }
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) => {
740
+ 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) => {
617
742
  const entryFile = path8.resolve(entry);
618
743
  const entryDir = path8.dirname(entryFile);
619
744
  const fileConfig = loadConfig(entryDir);
@@ -630,8 +755,8 @@ function createSnapshotCommand() {
630
755
  maxTypeDepth: parseInt(options.maxDepth ?? "4", 10),
631
756
  resolveExternalTypes: !options.skipResolve,
632
757
  schemaExtraction: options.runtime ? "hybrid" : "static",
633
- only: parseFilter(options.only),
634
- ignore: parseFilter(options.ignore),
758
+ only: parseList(options.only),
759
+ ignore: parseList(options.ignore),
635
760
  includePrivate: options.includePrivate,
636
761
  ...mergedConfig.externals && { externals: mergedConfig.externals }
637
762
  };
@@ -653,58 +778,15 @@ function createSnapshotCommand() {
653
778
  }
654
779
  }
655
780
  },
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
- }
781
+ ...externalExports.length > 0 && { external: { count: externalExports.length } }
674
782
  };
675
783
  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
784
  if (options.verify && result.verification && result.verification.failed > 0) {
702
- const errorOutput = {
785
+ console.error(JSON.stringify({
703
786
  error: "Export verification failed",
704
787
  failed: result.verification.details.failed,
705
788
  diagnostics: formatDiagnostics(result.diagnostics)
706
- };
707
- console.error(JSON.stringify(errorOutput, null, 2));
789
+ }, null, 2));
708
790
  process.exit(1);
709
791
  }
710
792
  const specJson = JSON.stringify(result.spec, null, 2);
@@ -717,40 +799,118 @@ function createSnapshotCommand() {
717
799
  }
718
800
  } catch (err) {
719
801
  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));
802
+ console.error(JSON.stringify({ error: error.message }, null, 2));
725
803
  process.exit(1);
726
804
  }
727
805
  });
728
806
  }
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) => {
807
+ function createValidateSubcommand() {
808
+ 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
809
  try {
743
- const spec = loadJSON2(specPath);
810
+ const spec = loadSpec6(specPath);
744
811
  const version = options.version ?? "latest";
745
812
  const errors = getValidationErrors(spec, version);
813
+ console.log(JSON.stringify({ valid: errors.length === 0, errors }, null, 2));
814
+ if (errors.length > 0)
815
+ process.exit(1);
816
+ } 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);
820
+ }
821
+ });
822
+ }
823
+ function createFilterSubcommand() {
824
+ 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
+ try {
826
+ const spec = loadSpec6(specPath);
827
+ const criteria = {};
828
+ if (options.kind) {
829
+ const kinds = parseList(options.kind);
830
+ if (kinds)
831
+ criteria.kinds = validateKinds(kinds);
832
+ }
833
+ if (options.name)
834
+ criteria.names = parseList(options.name);
835
+ if (options.id)
836
+ criteria.ids = parseList(options.id);
837
+ if (options.tag)
838
+ criteria.tags = parseList(options.tag);
839
+ if (options.deprecated !== undefined)
840
+ criteria.deprecated = options.deprecated;
841
+ if (options.hasDescription)
842
+ criteria.hasDescription = true;
843
+ if (options.missingDescription)
844
+ criteria.hasDescription = false;
845
+ if (options.search)
846
+ criteria.search = options.search;
847
+ if (options.searchMembers)
848
+ criteria.searchMembers = true;
849
+ if (options.searchDocs)
850
+ criteria.searchDocs = true;
851
+ if (options.module)
852
+ criteria.module = options.module;
853
+ const result = filterSpec(spec, criteria);
854
+ let output;
855
+ if (options.summary) {
856
+ output = { matched: result.matched, total: result.total };
857
+ } else if (options.quiet) {
858
+ output = result.spec;
859
+ } else {
860
+ output = { spec: result.spec, matched: result.matched, total: result.total };
861
+ }
862
+ const json = JSON.stringify(output, null, 2);
863
+ if (options.output) {
864
+ fs8.writeFileSync(path8.resolve(options.output), json);
865
+ } else {
866
+ console.log(json);
867
+ }
868
+ } 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
+ }
873
+ });
874
+ }
875
+ function createLintSubcommand() {
876
+ 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
+ try {
878
+ const spec = loadSpec6(specPath);
879
+ const diagnostics = analyzeSpec(spec);
880
+ const generation = spec.generation;
881
+ const skipped = generation?.skipped ?? [];
882
+ const externalExports = spec.exports.filter((e) => e.kind === "external");
883
+ const byReason = {};
884
+ for (const skip of skipped) {
885
+ byReason[skip.reason] = (byReason[skip.reason] ?? 0) + 1;
886
+ }
746
887
  const result = {
747
- valid: errors.length === 0,
748
- errors
888
+ summary: {
889
+ total: diagnostics.missingDescriptions.length + diagnostics.deprecatedNoReason.length + diagnostics.missingParamDocs.length,
890
+ missingDescriptions: diagnostics.missingDescriptions.length,
891
+ deprecatedNoReason: diagnostics.deprecatedNoReason.length,
892
+ missingParamDocs: diagnostics.missingParamDocs.length,
893
+ ...skipped.length > 0 && { skippedExports: skipped.length },
894
+ ...externalExports.length > 0 && { externalExports: externalExports.length }
895
+ },
896
+ diagnostics,
897
+ ...skipped.length > 0 && {
898
+ skippedExports: {
899
+ total: skipped.length,
900
+ byReason,
901
+ ...options.verbose && { details: skipped }
902
+ }
903
+ },
904
+ ...externalExports.length > 0 && {
905
+ externalExports: {
906
+ count: externalExports.length,
907
+ ...options.verbose && {
908
+ details: externalExports.map((e) => ({ name: e.name, package: e.source?.package }))
909
+ }
910
+ }
911
+ }
749
912
  };
750
913
  console.log(JSON.stringify(result, null, 2));
751
- if (errors.length > 0) {
752
- process.exit(1);
753
- }
754
914
  } catch (err) {
755
915
  const error = err instanceof Error ? err : new Error(String(err));
756
916
  console.error(JSON.stringify({ error: error.message }, null, 2));
@@ -758,40 +918,74 @@ function createValidateCommand() {
758
918
  }
759
919
  });
760
920
  }
921
+ function createListSubcommand() {
922
+ 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);
924
+ const result = await listExports({ entryFile });
925
+ if (result.errors.length > 0) {
926
+ console.error(JSON.stringify({ errors: result.errors }, null, 2));
927
+ process.exit(1);
928
+ }
929
+ console.log(JSON.stringify(result.exports, null, 2));
930
+ });
931
+ }
932
+ function createGetSubcommand() {
933
+ 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);
935
+ const result = await getExport({ entryFile, exportName: name });
936
+ if (!result.export) {
937
+ const errorMsg = result.errors.length > 0 ? result.errors.join("; ") : `Export '${name}' not found`;
938
+ console.error(JSON.stringify({ error: errorMsg }, null, 2));
939
+ process.exit(1);
940
+ }
941
+ const output = { export: result.export };
942
+ if (result.types.length > 0) {
943
+ output.types = result.types;
944
+ }
945
+ console.log(JSON.stringify(output, null, 2));
946
+ });
947
+ }
948
+ function createSpecCommand() {
949
+ const spec = new Command11("spec").description("Spec extraction and manipulation commands");
950
+ spec.addCommand(createSnapshotSubcommand());
951
+ spec.addCommand(createValidateSubcommand());
952
+ spec.addCommand(createFilterSubcommand());
953
+ spec.addCommand(createLintSubcommand());
954
+ spec.addCommand(createListSubcommand());
955
+ spec.addCommand(createGetSubcommand());
956
+ return spec;
957
+ }
761
958
 
762
959
  // bin/openpkg.ts
763
- var program = new Command10;
960
+ var program = new Command12;
764
961
  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());
962
+ program.addCommand(createSpecCommand());
790
963
  program.addCommand(createDocsCommand());
964
+ program.addCommand(createDiffCommand());
791
965
  program.addCommand(createBreakingCommand());
792
966
  program.addCommand(createChangelogCommand());
793
967
  program.addCommand(createSemverCommand());
794
- program.addCommand(createValidateCommand());
795
- program.addCommand(createDiagnosticsCommand());
796
- program.addCommand(createFilterCommand());
968
+ var specCmd = program.commands.find((c) => c.name() === "spec");
969
+ if (!specCmd) {
970
+ throw new Error("Internal error: spec command not found");
971
+ }
972
+ function getSubcommand(parent, name) {
973
+ const cmd = parent.commands.find((c) => c.name() === name);
974
+ if (!cmd) {
975
+ throw new Error(`Internal error: ${name} subcommand not found`);
976
+ }
977
+ return cmd;
978
+ }
979
+ function createAlias(aliasName, targetName, description) {
980
+ return new Command12(aliasName).description(description).allowUnknownOption().allowExcessArguments().action(async () => {
981
+ const args = process.argv.slice(3);
982
+ await getSubcommand(specCmd, targetName).parseAsync(args, { from: "user" });
983
+ });
984
+ }
985
+ program.addCommand(createAlias("snapshot", "snapshot", "(alias) → openpkg spec snapshot"));
986
+ program.addCommand(createAlias("list", "list", "(alias) → openpkg spec list"));
987
+ program.addCommand(createAlias("get", "get", "(alias) → openpkg spec get"));
988
+ program.addCommand(createAlias("validate", "validate", "(alias) → openpkg spec validate"));
989
+ program.addCommand(createAlias("filter", "filter", "(alias) → openpkg spec filter"));
990
+ program.addCommand(createAlias("diagnostics", "lint", "(alias) → openpkg spec lint"));
797
991
  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.1",
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.2",
27
- "@openpkg-ts/sdk": "^0.33.1",
26
+ "@openpkg-ts/adapters": "^0.3.4",
27
+ "@openpkg-ts/sdk": "^0.34.1",
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"