@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 +12 -0
- package/README.md +82 -85
- package/bin/openpkg.ts +61 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/package.json +6 -45
- package/src/commands/diff.ts +173 -0
- package/src/commands/docs.ts +122 -0
- package/src/commands/snapshot.ts +116 -0
- package/src/index.ts +2 -0
- package/test/diff.test.ts +336 -0
- package/test/docs.test.ts +599 -0
- package/test/get.test.ts +377 -0
- package/test/spec.test.ts +469 -0
- package/tsconfig.json +15 -0
- package/dist/cli.d.ts +0 -0
- package/dist/cli.js +0 -454
- package/dist/config/index.d.ts +0 -19
- package/dist/config/index.js +0 -110
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
|
-
#
|
|
1
|
+
# @openpkg-ts/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for TypeScript API extraction and documentation generation.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
5
|
+
## Install
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
# npm
|
|
11
8
|
npm install -g @openpkg-ts/cli
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
##
|
|
13
|
+
## Commands
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
# Generate openpkg.json for the current package
|
|
27
|
-
openpkg generate
|
|
15
|
+
### list
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
openpkg generate src/index.ts
|
|
17
|
+
List exports from entry point.
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
openpkg
|
|
19
|
+
```bash
|
|
20
|
+
openpkg list src/index.ts
|
|
34
21
|
```
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## Commands
|
|
23
|
+
Output: JSON array of `{ name, kind, file, line, description }`
|
|
39
24
|
|
|
40
|
-
###
|
|
25
|
+
### get
|
|
41
26
|
|
|
42
|
-
|
|
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
|
|
30
|
+
openpkg get src/index.ts createClient
|
|
49
31
|
```
|
|
50
32
|
|
|
51
|
-
|
|
33
|
+
Output: JSON with `{ export, types }` - full spec for the export plus referenced types.
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
- `--format <auto|mjs|js|cjs>` – Override the generated file extension.
|
|
35
|
+
### snapshot
|
|
55
36
|
|
|
56
|
-
|
|
37
|
+
Generate full OpenPkg spec from TypeScript.
|
|
57
38
|
|
|
58
|
-
|
|
39
|
+
```bash
|
|
40
|
+
# Write to file
|
|
41
|
+
openpkg snapshot src/index.ts -o openpkg.json
|
|
59
42
|
|
|
60
|
-
|
|
43
|
+
# Stdout (pipeable)
|
|
44
|
+
openpkg snapshot src/index.ts -o -
|
|
61
45
|
|
|
62
|
-
|
|
63
|
-
openpkg
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
+
Generate documentation from spec.
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
70
|
+
# HTML
|
|
71
|
+
openpkg docs openpkg.json -f html -o api.html
|
|
85
72
|
|
|
86
|
-
|
|
73
|
+
# JSON (simplified structure)
|
|
74
|
+
openpkg docs openpkg.json -f json
|
|
87
75
|
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
- `exclude: string[]` – Export identifiers to drop.
|
|
105
|
-
- `resolveExternalTypes?: boolean` – Override automatic detection of external type resolution.
|
|
90
|
+
### diff
|
|
106
91
|
|
|
107
|
-
|
|
92
|
+
Compare two specs for breaking changes.
|
|
108
93
|
|
|
109
|
-
|
|
94
|
+
```bash
|
|
95
|
+
openpkg diff old.json new.json
|
|
96
|
+
openpkg diff old.json new.json --summary
|
|
97
|
+
```
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
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
|
-
##
|
|
107
|
+
## Pipelines
|
|
116
108
|
|
|
117
|
-
|
|
109
|
+
Commands are composable via stdin/stdout:
|
|
118
110
|
|
|
119
111
|
```bash
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
# Extract and generate docs
|
|
113
|
+
openpkg snapshot src/index.ts -o - | openpkg docs - -f md > api.md
|
|
122
114
|
|
|
123
|
-
|
|
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
|
-
|
|
120
|
+
## Programmatic Use
|
|
126
121
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- Any diagnostics collected during analysis.
|
|
122
|
+
```typescript
|
|
123
|
+
import { getExport, listExports } from '@openpkg-ts/cli';
|
|
130
124
|
|
|
131
|
-
|
|
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();
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,58 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpkg-ts/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
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": "./
|
|
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
|
-
"
|
|
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
|
-
"@
|
|
50
|
-
"
|
|
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
|
+
}
|