@openpkg-ts/cli 0.1.0 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # @openpkg-ts/cli
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Major monorepo restructure: extract → sdk, doc-generator split into sdk/react/adapters, fumadocs-adapter deleted (use @openpkg-ts/adapters/fumadocs)
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @openpkg-ts/sdk@0.30.0
package/README.md CHANGED
@@ -1,134 +1,131 @@
1
- # OpenPkg CLI
1
+ # @openpkg-ts/cli
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@openpkg-ts%2Fcli.svg)](https://www.npmjs.com/package/@openpkg-ts/cli)
3
+ CLI for TypeScript API extraction and documentation generation.
4
4
 
5
- Command-line interface for producing OpenPkg specs from TypeScript projects.
6
-
7
- ## Installation
5
+ ## Install
8
6
 
9
7
  ```bash
10
- # npm
11
8
  npm install -g @openpkg-ts/cli
12
-
13
- # bun
14
- bun add -g @openpkg-ts/cli
15
-
16
- # yarn
17
- yarn global add @openpkg-ts/cli
18
-
19
- # pnpm
20
- pnpm add -g @openpkg-ts/cli
9
+ # or use directly
10
+ npx @openpkg-ts/cli <command>
21
11
  ```
22
12
 
23
- ## Quick Start
13
+ ## Commands
24
14
 
25
- ```bash
26
- # Generate openpkg.json for the current package
27
- openpkg generate
15
+ ### list
28
16
 
29
- # Target a specific entry file
30
- openpkg generate src/index.ts
17
+ List exports from entry point.
31
18
 
32
- # Scaffold an OpenPkg config
33
- openpkg init
19
+ ```bash
20
+ openpkg list src/index.ts
34
21
  ```
35
22
 
36
- `openpkg generate` discovers the package manifest, figures out the correct entry point, resolves external .d.ts files when `node_modules` is present, and writes `openpkg.json` by default.
37
-
38
- ## Commands
23
+ Output: JSON array of `{ name, kind, file, line, description }`
39
24
 
40
- ### `openpkg init`
25
+ ### get
41
26
 
42
- Create a starter `openpkg.config` file in the current project. The CLI picks an extension automatically:
43
-
44
- - `openpkg.config.js` when the nearest `package.json` declares `{ "type": "module" }`
45
- - `openpkg.config.mjs` otherwise (compatible with both ESM and CommonJS projects)
27
+ Get detailed spec for single export.
46
28
 
47
29
  ```bash
48
- openpkg init --cwd . --format auto
30
+ openpkg get src/index.ts createClient
49
31
  ```
50
32
 
51
- Options:
33
+ Output: JSON with `{ export, types }` - full spec for the export plus referenced types.
52
34
 
53
- - `--cwd <dir>` – Directory where the config should be created (defaults to current directory).
54
- - `--format <auto|mjs|js|cjs>` – Override the generated file extension.
35
+ ### snapshot
55
36
 
56
- The command aborts when a config already exists anywhere up the directory tree.
37
+ Generate full OpenPkg spec from TypeScript.
57
38
 
58
- ### `openpkg generate [entry]`
39
+ ```bash
40
+ # Write to file
41
+ openpkg snapshot src/index.ts -o openpkg.json
59
42
 
60
- Generate an OpenPkg spec from a file or package entry point.
43
+ # Stdout (pipeable)
44
+ openpkg snapshot src/index.ts -o -
61
45
 
62
- ```bash
63
- openpkg generate src/index.ts --output lib/openpkg.json --include=createUser
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"
64
49
  ```
65
50
 
66
- Key behaviors:
51
+ Options:
52
+ | Flag | Description |
53
+ |------|-------------|
54
+ | `-o, --output <file>` | Output file (default: openpkg.json, `-` for stdout) |
55
+ | `--max-depth <n>` | Max type depth (default: 4) |
56
+ | `--skip-resolve` | Skip external type resolution |
57
+ | `--runtime` | Enable Standard Schema runtime extraction (Zod, Valibot) |
58
+ | `--only <exports>` | Filter exports (comma-separated, wildcards) |
59
+ | `--ignore <exports>` | Ignore exports (comma-separated, wildcards) |
60
+ | `--verify` | Exit 1 if any exports fail |
67
61
 
68
- - Auto-detects the entry point when `[entry]` is omitted (using `exports`, `main`, or TypeScript config fields).
69
- - Honors `openpkg.config.*` defaults and then applies CLI flags on top.
70
- - Emits diagnostics from the TypeScript compiler and from OpenPkg's filtering passes.
71
- - Writes formatted JSON to `openpkg.json` (or the path supplied via `--output`).
62
+ ### docs
72
63
 
73
- #### Options
64
+ Generate documentation from spec.
74
65
 
75
- - `[entry]` – Entry file to analyze. Optional when the package exposes a single entry point.
76
- - `-o, --output <file>` – Output path (default: `openpkg.json`).
77
- - `-p, --package <name>` – Resolve and analyze a workspace package by name.
78
- - `--cwd <dir>` – Base directory for resolution (default: current directory).
79
- - `--no-external-types` – Skip pulling types from `node_modules`.
80
- - `--include <ids>` – Keep only the listed export identifiers (comma-separated or repeatable).
81
- - `--exclude <ids>` – Drop the listed export identifiers.
82
- - `-y, --yes` – Assume "yes" for prompts.
66
+ ```bash
67
+ # Markdown (default)
68
+ openpkg docs openpkg.json -o api.md
83
69
 
84
- ## Configuration File
70
+ # HTML
71
+ openpkg docs openpkg.json -f html -o api.html
85
72
 
86
- Create an `openpkg.config.ts`, `.js`, or `.mjs` file anywhere above your working directory to keep reusable defaults. Prefer `.mjs`/`.cjs` if you are running the CLI under Node.js without a TypeScript loader.
73
+ # JSON (simplified structure)
74
+ openpkg docs openpkg.json -f json
87
75
 
88
- ```ts
89
- // openpkg.config.mjs
90
- import { defineConfig } from '@openpkg-ts/cli/config';
76
+ # Split: one file per export
77
+ openpkg docs openpkg.json --split -o docs/api/
91
78
 
92
- export default defineConfig({
93
- include: ['createUser', 'deleteUser'],
94
- exclude: ['internalHelper'],
95
- resolveExternalTypes: true,
96
- });
79
+ # Pipeline: stdin
80
+ openpkg snapshot src/index.ts -o - | openpkg docs - -f md
97
81
  ```
98
82
 
99
- The CLI searches the current directory and its parents for the first config file and merges those settings with flags provided on the command line. `defineConfig` helps with type-safety but is optional—you can export a plain object as well.
100
-
101
- ### Supported Options
83
+ Options:
84
+ | Flag | Description |
85
+ |------|-------------|
86
+ | `-o, --output <path>` | Output file or directory (default: stdout) |
87
+ | `-f, --format <fmt>` | Format: `md`, `json`, `html` (default: md) |
88
+ | `--split` | One file per export (requires `-o` as directory) |
102
89
 
103
- - `include: string[]` – Export identifiers to keep.
104
- - `exclude: string[]` – Export identifiers to drop.
105
- - `resolveExternalTypes?: boolean` – Override automatic detection of external type resolution.
90
+ ### diff
106
91
 
107
- CLI flags always win over config values. When both provide filters, the CLI prints a short summary of how the sets were combined.
92
+ Compare two specs for breaking changes.
108
93
 
109
- ## Filtering Tips
94
+ ```bash
95
+ openpkg diff old.json new.json
96
+ openpkg diff old.json new.json --summary
97
+ ```
110
98
 
111
- - `--include` narrows the spec to the identifiers you care about. Any referenced types that fall outside the allow-list are removed unless they are still referenced.
112
- - `--exclude` is useful for dropping experimental or internal APIs while keeping everything else.
113
- - Combine filters in configuration for defaults and override per run via CLI flags.
99
+ Output includes:
100
+ - `breaking` - categorized breaking changes
101
+ - `added` - new exports
102
+ - `removed` - removed exports
103
+ - `changed` - modified exports
104
+ - `docsOnly` - documentation-only changes
105
+ - `summary.semverBump` - recommended version bump
114
106
 
115
- ## Monorepo Support
107
+ ## Pipelines
116
108
 
117
- Supply `--package <name>` from the workspace root to locate a child package automatically. The CLI understands npm, pnpm, yarn, and bun workspace layouts.
109
+ Commands are composable via stdin/stdout:
118
110
 
119
111
  ```bash
120
- openpkg generate --package @myorg/transactions
121
- ```
112
+ # Extract and generate docs
113
+ openpkg snapshot src/index.ts -o - | openpkg docs - -f md > api.md
122
114
 
123
- ## Output
115
+ # Extract, verify, then diff
116
+ openpkg snapshot src/index.ts --verify -o new.json
117
+ openpkg diff baseline.json new.json --summary
118
+ ```
124
119
 
125
- After a successful run the CLI prints:
120
+ ## Programmatic Use
126
121
 
127
- - The relative path to the written spec.
128
- - Counts for exports and types earned after filtering.
129
- - Any diagnostics collected during analysis.
122
+ ```typescript
123
+ import { getExport, listExports } from '@openpkg-ts/cli';
130
124
 
131
- The JSON schema for the output lives at `schemas/v0.1.0/openpkg.schema.json` in this repository.
125
+ // Same primitives as CLI
126
+ const { exports } = await listExports({ entryFile: './src/index.ts' });
127
+ const { export: spec } = await getExport({ entryFile: './src/index.ts', exportName: 'myFunc' });
128
+ ```
132
129
 
133
130
  ## License
134
131
 
package/bin/openpkg.ts ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bun
2
+ import { Command } from 'commander';
3
+ import { getExport, listExports } from '@openpkg-ts/sdk';
4
+ import { createSnapshotCommand } from '../src/commands/snapshot';
5
+ import { createDiffCommand } from '../src/commands/diff';
6
+ import { createDocsCommand } from '../src/commands/docs';
7
+ import * as path from 'node:path';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('openpkg')
13
+ .description('OpenPkg CLI - TypeScript API extraction primitives')
14
+ .version('0.1.0');
15
+
16
+ program
17
+ .command('list')
18
+ .description('List exports from a TypeScript entry point')
19
+ .argument('<entry>', 'Entry point file path')
20
+ .action(async (entry: string) => {
21
+ const entryFile = path.resolve(entry);
22
+ const result = await listExports({ entryFile });
23
+
24
+ if (result.errors.length > 0) {
25
+ console.error(JSON.stringify({ errors: result.errors }, null, 2));
26
+ process.exit(1);
27
+ }
28
+
29
+ console.log(JSON.stringify(result.exports, null, 2));
30
+ });
31
+
32
+ program
33
+ .command('get')
34
+ .description('Get detailed spec for a single export')
35
+ .argument('<entry>', 'Entry point file path')
36
+ .argument('<name>', 'Export name')
37
+ .action(async (entry: string, name: string) => {
38
+ const entryFile = path.resolve(entry);
39
+ const result = await getExport({ entryFile, exportName: name });
40
+
41
+ if (!result.export) {
42
+ const errorMsg = result.errors.length > 0
43
+ ? result.errors.join('; ')
44
+ : `Export '${name}' not found`;
45
+ console.error(JSON.stringify({ error: errorMsg }, null, 2));
46
+ process.exit(1);
47
+ }
48
+
49
+ // Output export with related types if any
50
+ const output: Record<string, unknown> = { export: result.export };
51
+ if (result.types.length > 0) {
52
+ output.types = result.types;
53
+ }
54
+ console.log(JSON.stringify(output, null, 2));
55
+ });
56
+
57
+ program.addCommand(createSnapshotCommand());
58
+ program.addCommand(createDiffCommand());
59
+ program.addCommand(createDocsCommand());
60
+
61
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import { getExport, listExports } from "@openpkg-ts/sdk";
2
+ export { listExports, getExport };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // src/index.ts
2
+ import { getExport, listExports } from "@openpkg-ts/sdk";
3
+ export {
4
+ listExports,
5
+ getExport
6
+ };
package/package.json CHANGED
@@ -1,58 +1,19 @@
1
1
  {
2
2
  "name": "@openpkg-ts/cli",
3
- "version": "0.1.0",
4
- "description": "OpenAPI-like specification generator for TypeScript packages",
5
- "keywords": [
6
- "typescript",
7
- "cli",
8
- "documentation",
9
- "openpkg",
10
- "package-analysis",
11
- "openapi"
12
- ],
13
- "homepage": "https://github.com/openpkg/openpkg#readme",
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://github.com/openpkg/openpkg.git",
17
- "directory": "packages/cli"
18
- },
19
- "license": "MIT",
20
- "author": "Ryan Waits",
3
+ "version": "0.2.0",
4
+ "description": "CLI for OpenPkg TypeScript API extraction",
21
5
  "type": "module",
22
- "main": "./dist/index.js",
23
- "types": "./dist/index.d.ts",
24
6
  "bin": {
25
- "openpkg": "./dist/cli.js"
26
- },
27
- "exports": {
28
- ".": {
29
- "import": "./dist/index.js",
30
- "types": "./dist/index.d.ts"
31
- },
32
- "./config": {
33
- "import": "./dist/config/index.js",
34
- "types": "./dist/config/index.d.ts"
35
- }
7
+ "openpkg": "./bin/openpkg.ts"
36
8
  },
37
9
  "scripts": {
38
10
  "build": "bunup",
39
11
  "dev": "bunup --watch",
40
- "cli": "bun run src/cli.ts",
41
- "lint": "biome check src/",
42
- "lint:fix": "biome check --write src/",
43
- "format": "biome format --write src/"
12
+ "test": "bun test"
44
13
  },
45
- "files": [
46
- "dist"
47
- ],
48
14
  "dependencies": {
49
- "@inquirer/prompts": "^7.8.0",
50
- "@openpkg-ts/sdk": "^0.1.0",
51
- "@openpkg-ts/spec": "^0.1.0",
52
- "chalk": "^5.4.1",
53
- "commander": "^14.0.0",
54
- "ora": "^8.2.0",
55
- "zod": "^4.0.5"
15
+ "@openpkg-ts/sdk": "workspace:*",
16
+ "commander": "^14.0.0"
56
17
  },
57
18
  "devDependencies": {
58
19
  "@types/bun": "latest",
@@ -0,0 +1,173 @@
1
+ import { Command } from 'commander';
2
+ import {
3
+ diffSpec,
4
+ categorizeBreakingChanges,
5
+ recommendSemverBump,
6
+ type OpenPkg,
7
+ type CategorizedBreaking,
8
+ type SemverBump,
9
+ type SpecExportKind,
10
+ } from '@openpkg-ts/spec';
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+
14
+ /**
15
+ * A changed export with details about what changed
16
+ */
17
+ export interface ChangedExport {
18
+ id: string;
19
+ name: string;
20
+ kind: SpecExportKind;
21
+ description: string;
22
+ }
23
+
24
+ /**
25
+ * Enriched diff result with categorized changes
26
+ */
27
+ export interface DiffResult {
28
+ breaking: CategorizedBreaking[];
29
+ added: string[];
30
+ removed: RemovedExport[];
31
+ changed: ChangedExport[];
32
+ docsOnly: string[];
33
+ summary: {
34
+ breakingCount: number;
35
+ addedCount: number;
36
+ removedCount: number;
37
+ changedCount: number;
38
+ docsOnlyCount: number;
39
+ semverBump: SemverBump;
40
+ semverReason: string;
41
+ };
42
+ }
43
+
44
+ export interface RemovedExport {
45
+ id: string;
46
+ name: string;
47
+ kind: SpecExportKind;
48
+ }
49
+
50
+ function loadSpec(filePath: string): OpenPkg {
51
+ const resolved = path.resolve(filePath);
52
+ const content = fs.readFileSync(resolved, 'utf-8');
53
+ return JSON.parse(content) as OpenPkg;
54
+ }
55
+
56
+ function toExportMap(spec: OpenPkg): Map<string, { name: string; kind: SpecExportKind }> {
57
+ const map = new Map<string, { name: string; kind: SpecExportKind }>();
58
+ for (const exp of spec.exports) {
59
+ map.set(exp.id, { name: exp.name, kind: exp.kind });
60
+ }
61
+ if (spec.types) {
62
+ for (const t of spec.types) {
63
+ map.set(t.id, { name: t.name, kind: t.kind as SpecExportKind });
64
+ }
65
+ }
66
+ return map;
67
+ }
68
+
69
+ /**
70
+ * Enriches basic diffSpec output with categorization
71
+ */
72
+ export function enrichDiff(oldSpec: OpenPkg, newSpec: OpenPkg): DiffResult {
73
+ const rawDiff = diffSpec(oldSpec, newSpec);
74
+ const categorized = categorizeBreakingChanges(rawDiff.breaking, oldSpec, newSpec);
75
+ const semver = recommendSemverBump(rawDiff);
76
+
77
+ const oldExports = toExportMap(oldSpec);
78
+ const newExports = toExportMap(newSpec);
79
+
80
+ // Separate removed from changed
81
+ const removed: RemovedExport[] = [];
82
+ const changed: ChangedExport[] = [];
83
+ const breaking: CategorizedBreaking[] = [];
84
+
85
+ for (const cat of categorized) {
86
+ if (cat.reason === 'removed') {
87
+ const info = oldExports.get(cat.id);
88
+ removed.push({
89
+ id: cat.id,
90
+ name: cat.name,
91
+ kind: info?.kind ?? cat.kind,
92
+ });
93
+ } else {
94
+ // It's a change, not a removal
95
+ changed.push({
96
+ id: cat.id,
97
+ name: cat.name,
98
+ kind: cat.kind,
99
+ description: describeChange(cat),
100
+ });
101
+ breaking.push(cat);
102
+ }
103
+ }
104
+
105
+ // Added exports
106
+ const added = rawDiff.nonBreaking;
107
+
108
+ return {
109
+ breaking,
110
+ added,
111
+ removed,
112
+ changed,
113
+ docsOnly: rawDiff.docsOnly,
114
+ summary: {
115
+ breakingCount: breaking.length,
116
+ addedCount: added.length,
117
+ removedCount: removed.length,
118
+ changedCount: changed.length,
119
+ docsOnlyCount: rawDiff.docsOnly.length,
120
+ semverBump: semver.bump,
121
+ semverReason: semver.reason,
122
+ },
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Generate human-readable description for a change
128
+ */
129
+ function describeChange(cat: CategorizedBreaking): string {
130
+ switch (cat.reason) {
131
+ case 'signature changed':
132
+ return `Function signature changed`;
133
+ case 'type definition changed':
134
+ return `Type definition changed`;
135
+ case 'constructor changed':
136
+ return `Class constructor signature changed`;
137
+ case 'methods removed':
138
+ return `Class methods removed`;
139
+ case 'methods changed':
140
+ return `Class methods changed`;
141
+ case 'changed':
142
+ return `${cat.kind} changed`;
143
+ default:
144
+ return cat.reason;
145
+ }
146
+ }
147
+
148
+ export function createDiffCommand(): Command {
149
+ return new Command('diff')
150
+ .description('Compare two OpenPkg specs and show differences')
151
+ .argument('<old>', 'Path to old spec file (JSON)')
152
+ .argument('<new>', 'Path to new spec file (JSON)')
153
+ .option('--json', 'Output as JSON (default)')
154
+ .option('--summary', 'Only show summary')
155
+ .action(async (oldPath: string, newPath: string, options: { json?: boolean; summary?: boolean }) => {
156
+ try {
157
+ const oldSpec = loadSpec(oldPath);
158
+ const newSpec = loadSpec(newPath);
159
+
160
+ const result = enrichDiff(oldSpec, newSpec);
161
+
162
+ if (options.summary) {
163
+ console.log(JSON.stringify(result.summary, null, 2));
164
+ } else {
165
+ console.log(JSON.stringify(result, null, 2));
166
+ }
167
+ } catch (err) {
168
+ const error = err instanceof Error ? err : new Error(String(err));
169
+ console.error(JSON.stringify({ error: error.message }, null, 2));
170
+ process.exit(1);
171
+ }
172
+ });
173
+ }
@@ -0,0 +1,122 @@
1
+ import { Command } from 'commander';
2
+ import { createDocs, loadSpec, type DocsInstance } from '@openpkg-ts/sdk';
3
+ import type { OpenPkg } from '@openpkg-ts/spec';
4
+ import * as path from 'node:path';
5
+ import * as fs from 'node:fs';
6
+
7
+ type OutputFormat = 'md' | 'json' | 'html';
8
+
9
+ interface DocsCommandOptions {
10
+ output?: string;
11
+ format?: OutputFormat;
12
+ split?: boolean;
13
+ }
14
+
15
+ async function readStdin(): Promise<string> {
16
+ const chunks: Buffer[] = [];
17
+ for await (const chunk of process.stdin) {
18
+ chunks.push(chunk);
19
+ }
20
+ return Buffer.concat(chunks).toString('utf-8');
21
+ }
22
+
23
+ function getExtension(format: OutputFormat): string {
24
+ switch (format) {
25
+ case 'json': return '.json';
26
+ case 'html': return '.html';
27
+ default: return '.md';
28
+ }
29
+ }
30
+
31
+ function renderExport(docs: DocsInstance, exportId: string, format: OutputFormat): string {
32
+ const exp = docs.getExport(exportId);
33
+ if (!exp) throw new Error(`Export not found: ${exportId}`);
34
+
35
+ switch (format) {
36
+ case 'json':
37
+ return JSON.stringify(docs.toJSON({ exportId }), null, 2);
38
+ case 'html':
39
+ return docs.toHTML({ exportId });
40
+ default:
41
+ return docs.toMarkdown({ exportId, frontmatter: true, codeSignatures: true });
42
+ }
43
+ }
44
+
45
+ function renderFull(docs: DocsInstance, format: OutputFormat): string {
46
+ switch (format) {
47
+ case 'json':
48
+ return JSON.stringify(docs.toJSON(), null, 2);
49
+ case 'html':
50
+ return docs.toHTML();
51
+ default:
52
+ return docs.toMarkdown({ frontmatter: true, codeSignatures: true });
53
+ }
54
+ }
55
+
56
+ export function createDocsCommand(): Command {
57
+ return new Command('docs')
58
+ .description('Generate documentation from OpenPkg spec')
59
+ .argument('<spec>', 'Path to openpkg.json spec file (use - for stdin)')
60
+ .option('-o, --output <path>', 'Output file or directory (default: stdout)')
61
+ .option('-f, --format <format>', 'Output format: md, json, html (default: md)', 'md')
62
+ .option('--split', 'Output one file per export (requires --output as directory)')
63
+ .action(async (specPath: string, options: DocsCommandOptions) => {
64
+ const format = (options.format || 'md') as OutputFormat;
65
+
66
+ try {
67
+ let docs: DocsInstance;
68
+
69
+ // Handle stdin
70
+ if (specPath === '-') {
71
+ const input = await readStdin();
72
+ const spec: OpenPkg = JSON.parse(input);
73
+ docs = loadSpec(spec);
74
+ } else {
75
+ const specFile = path.resolve(specPath);
76
+ if (!fs.existsSync(specFile)) {
77
+ console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
78
+ process.exit(1);
79
+ }
80
+ docs = createDocs(specFile);
81
+ }
82
+
83
+ // Split mode: one file per export
84
+ if (options.split) {
85
+ if (!options.output) {
86
+ console.error(JSON.stringify({ error: '--split requires --output <directory>' }));
87
+ process.exit(1);
88
+ }
89
+
90
+ const outDir = path.resolve(options.output);
91
+ if (!fs.existsSync(outDir)) {
92
+ fs.mkdirSync(outDir, { recursive: true });
93
+ }
94
+
95
+ const exports = docs.getAllExports();
96
+ for (const exp of exports) {
97
+ const filename = `${exp.name}${getExtension(format)}`;
98
+ const filePath = path.join(outDir, filename);
99
+ const content = renderExport(docs, exp.id, format);
100
+ fs.writeFileSync(filePath, content);
101
+ }
102
+ console.error(`Wrote ${exports.length} files to ${outDir}`);
103
+ return;
104
+ }
105
+
106
+ // Single output mode
107
+ const output = renderFull(docs, format);
108
+
109
+ if (options.output && options.output !== '-') {
110
+ const outputPath = path.resolve(options.output);
111
+ fs.writeFileSync(outputPath, output);
112
+ console.error(`Wrote ${outputPath}`);
113
+ } else {
114
+ console.log(output);
115
+ }
116
+ } catch (err) {
117
+ const error = err instanceof Error ? err : new Error(String(err));
118
+ console.error(JSON.stringify({ error: error.message }));
119
+ process.exit(1);
120
+ }
121
+ });
122
+ }