@openpkg-ts/cli 0.3.0 → 0.3.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.
@@ -1,63 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import * as fs from 'node:fs';
3
- import * as os from 'node:os';
4
- import * as path from 'node:path';
5
- import type { OpenPkg } from '@openpkg-ts/spec';
6
- import { $ } from 'bun';
7
-
8
- const testSpec: OpenPkg = {
9
- meta: { name: 'test-pkg', version: '1.0.0' },
10
- exports: [
11
- { id: 'fn-hello', name: 'hello', kind: 'function', signatures: [] },
12
- { id: 'fn-world', name: 'world', kind: 'function', signatures: [] },
13
- ],
14
- types: [],
15
- };
16
-
17
- describe('docs command --adapter', () => {
18
- test('--adapter fumadocs generates docs', async () => {
19
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docs-adapter-test-'));
20
- const specPath = path.join(tmpDir, 'spec.json');
21
- const outDir = path.join(tmpDir, 'out');
22
-
23
- fs.writeFileSync(specPath, JSON.stringify(testSpec));
24
-
25
- await $`bun packages/cli/bin/openpkg.ts docs ${specPath} --adapter fumadocs --output ${outDir}`.text();
26
-
27
- expect(fs.existsSync(outDir)).toBe(true);
28
- expect(fs.existsSync(path.join(outDir, 'hello.md'))).toBe(true);
29
- expect(fs.existsSync(path.join(outDir, 'world.md'))).toBe(true);
30
-
31
- fs.rmSync(tmpDir, { recursive: true });
32
- });
33
-
34
- test('--adapter raw falls through to default', async () => {
35
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docs-adapter-test-'));
36
- const specPath = path.join(tmpDir, 'spec.json');
37
-
38
- fs.writeFileSync(specPath, JSON.stringify(testSpec));
39
-
40
- const result = await $`bun packages/cli/bin/openpkg.ts docs ${specPath} --adapter raw`.text();
41
-
42
- expect(result).toContain('hello');
43
-
44
- fs.rmSync(tmpDir, { recursive: true });
45
- });
46
-
47
- test('--adapter unknown exits with error', async () => {
48
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docs-adapter-test-'));
49
- const specPath = path.join(tmpDir, 'spec.json');
50
- const outDir = path.join(tmpDir, 'out');
51
-
52
- fs.writeFileSync(specPath, JSON.stringify(testSpec));
53
-
54
- const result = await $`bun packages/cli/bin/openpkg.ts docs ${specPath} --adapter unknown --output ${outDir}`.nothrow();
55
-
56
- expect(result.exitCode).toBe(1);
57
- // Error is written to stderr as JSON
58
- const stderr = result.stderr.toString();
59
- expect(stderr).toContain('Failed to load adapter');
60
-
61
- fs.rmSync(tmpDir, { recursive: true });
62
- });
63
- });
@@ -1,191 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import { createDocs, type DocsInstance, loadSpec } from '@openpkg-ts/sdk';
4
- import type { OpenPkg } from '@openpkg-ts/spec';
5
- import { Command } from 'commander';
6
-
7
- type OutputFormat = 'md' | 'json' | 'html';
8
-
9
- interface DocsCommandOptions {
10
- output?: string;
11
- format?: OutputFormat;
12
- split?: boolean;
13
- export?: string;
14
- adapter?: string;
15
- }
16
-
17
- async function readStdin(): Promise<string> {
18
- const chunks: Buffer[] = [];
19
- for await (const chunk of process.stdin) {
20
- chunks.push(chunk);
21
- }
22
- return Buffer.concat(chunks).toString('utf-8');
23
- }
24
-
25
- function getExtension(format: OutputFormat): string {
26
- switch (format) {
27
- case 'json':
28
- return '.json';
29
- case 'html':
30
- return '.html';
31
- default:
32
- return '.md';
33
- }
34
- }
35
-
36
- function renderExport(docs: DocsInstance, exportId: string, format: OutputFormat): string {
37
- const exp = docs.getExport(exportId);
38
- if (!exp) throw new Error(`Export not found: ${exportId}`);
39
-
40
- switch (format) {
41
- case 'json':
42
- return JSON.stringify(docs.toJSON({ export: exportId }), null, 2);
43
- case 'html':
44
- return docs.toHTML({ export: exportId });
45
- default:
46
- return docs.toMarkdown({ export: exportId, frontmatter: true, codeSignatures: true });
47
- }
48
- }
49
-
50
- function renderFull(docs: DocsInstance, format: OutputFormat): string {
51
- switch (format) {
52
- case 'json':
53
- return JSON.stringify(docs.toJSON(), null, 2);
54
- case 'html':
55
- return docs.toHTML();
56
- default:
57
- return docs.toMarkdown({ frontmatter: true, codeSignatures: true });
58
- }
59
- }
60
-
61
- export function createDocsCommand(): Command {
62
- return new Command('docs')
63
- .description('Generate documentation from OpenPkg spec')
64
- .argument('<spec>', 'Path to openpkg.json spec file (use - for stdin)')
65
- .option('-o, --output <path>', 'Output file or directory (default: stdout)')
66
- .option('-f, --format <format>', 'Output format: md, json, html (default: md)', 'md')
67
- .option('--split', 'Output one file per export (requires --output as directory)')
68
- .option('-e, --export <name>', 'Generate docs for a single export by name')
69
- .option('-a, --adapter <name>', 'Use adapter for generation (default: raw)')
70
- .action(async (specPath: string, options: DocsCommandOptions) => {
71
- const format = (options.format || 'md') as OutputFormat;
72
-
73
- try {
74
- // Handle adapter mode
75
- if (options.adapter && options.adapter !== 'raw') {
76
- // Dynamic import adapter to trigger self-registration
77
- let getAdapter: typeof import('@openpkg-ts/adapters').getAdapter;
78
- try {
79
- const adapterModule = await import(`@openpkg-ts/adapters/${options.adapter}`);
80
- const registryModule = await import('@openpkg-ts/adapters');
81
- getAdapter = registryModule.getAdapter;
82
- } catch {
83
- console.error(JSON.stringify({ error: `Failed to load adapter: ${options.adapter}` }));
84
- process.exit(1);
85
- }
86
-
87
- const adapter = getAdapter(options.adapter);
88
- if (!adapter) {
89
- console.error(JSON.stringify({ error: `Unknown adapter: ${options.adapter}` }));
90
- process.exit(1);
91
- }
92
-
93
- if (!options.output) {
94
- console.error(JSON.stringify({ error: '--adapter requires --output <directory>' }));
95
- process.exit(1);
96
- }
97
-
98
- // Load spec
99
- let spec: OpenPkg;
100
- if (specPath === '-') {
101
- const input = await readStdin();
102
- spec = JSON.parse(input);
103
- } else {
104
- const specFile = path.resolve(specPath);
105
- if (!fs.existsSync(specFile)) {
106
- console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
107
- process.exit(1);
108
- }
109
- spec = JSON.parse(fs.readFileSync(specFile, 'utf-8'));
110
- }
111
-
112
- await adapter.generate(spec, path.resolve(options.output));
113
- console.error(`Generated docs with ${options.adapter} adapter to ${options.output}`);
114
- return;
115
- }
116
-
117
- let docs: DocsInstance;
118
-
119
- // Handle stdin
120
- if (specPath === '-') {
121
- const input = await readStdin();
122
- const spec: OpenPkg = JSON.parse(input);
123
- docs = loadSpec(spec);
124
- } else {
125
- const specFile = path.resolve(specPath);
126
- if (!fs.existsSync(specFile)) {
127
- console.error(JSON.stringify({ error: `Spec file not found: ${specFile}` }));
128
- process.exit(1);
129
- }
130
- docs = createDocs(specFile);
131
- }
132
-
133
- // Single export mode
134
- if (options.export) {
135
- const exports = docs.getAllExports();
136
- const exp = exports.find(e => e.name === options.export);
137
- if (!exp) {
138
- console.error(JSON.stringify({ error: `Export not found: ${options.export}` }));
139
- process.exit(1);
140
- }
141
- const output = renderExport(docs, exp.id, format);
142
- if (options.output && options.output !== '-') {
143
- const outputPath = path.resolve(options.output);
144
- fs.writeFileSync(outputPath, output);
145
- console.error(`Wrote ${outputPath}`);
146
- } else {
147
- console.log(output);
148
- }
149
- return;
150
- }
151
-
152
- // Split mode: one file per export
153
- if (options.split) {
154
- if (!options.output) {
155
- console.error(JSON.stringify({ error: '--split requires --output <directory>' }));
156
- process.exit(1);
157
- }
158
-
159
- const outDir = path.resolve(options.output);
160
- if (!fs.existsSync(outDir)) {
161
- fs.mkdirSync(outDir, { recursive: true });
162
- }
163
-
164
- const exports = docs.getAllExports();
165
- for (const exp of exports) {
166
- const filename = `${exp.name}${getExtension(format)}`;
167
- const filePath = path.join(outDir, filename);
168
- const content = renderExport(docs, exp.id, format);
169
- fs.writeFileSync(filePath, content);
170
- }
171
- console.error(`Wrote ${exports.length} files to ${outDir}`);
172
- return;
173
- }
174
-
175
- // Single output mode
176
- const output = renderFull(docs, format);
177
-
178
- if (options.output && options.output !== '-') {
179
- const outputPath = path.resolve(options.output);
180
- fs.writeFileSync(outputPath, output);
181
- console.error(`Wrote ${outputPath}`);
182
- } else {
183
- console.log(output);
184
- }
185
- } catch (err) {
186
- const error = err instanceof Error ? err : new Error(String(err));
187
- console.error(JSON.stringify({ error: error.message }));
188
- process.exit(1);
189
- }
190
- });
191
- }
@@ -1,210 +0,0 @@
1
- import { afterAll, beforeAll, describe, expect, it } from 'bun:test';
2
- import * as fs from 'node:fs';
3
- import * as os from 'node:os';
4
- import * as path from 'node:path';
5
- import { $ } from 'bun';
6
-
7
- describe('openpkg filter', () => {
8
- let tmpDir: string;
9
- let specPath: string;
10
-
11
- const spec = {
12
- openpkg: '0.4.0',
13
- meta: { name: 'test-pkg' },
14
- exports: [
15
- { id: 'a', name: 'myFunction', kind: 'function', description: 'A function' },
16
- { id: 'b', name: 'MyClass', kind: 'class', description: 'A class' },
17
- { id: 'c', name: 'myVar', kind: 'variable' },
18
- { id: 'd', name: 'deprecatedFn', kind: 'function', deprecated: true, description: 'Old' },
19
- {
20
- id: 'e',
21
- name: 'taggedFn',
22
- kind: 'function',
23
- tags: [{ name: 'beta' }],
24
- description: 'Beta feature',
25
- },
26
- {
27
- id: 'f',
28
- name: 'anotherClass',
29
- kind: 'class',
30
- source: { file: 'src/utils/helpers.ts' },
31
- },
32
- ],
33
- };
34
-
35
- beforeAll(() => {
36
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filter-test-'));
37
- specPath = path.join(tmpDir, 'spec.json');
38
- fs.writeFileSync(specPath, JSON.stringify(spec));
39
- });
40
-
41
- afterAll(() => {
42
- fs.rmSync(tmpDir, { recursive: true });
43
- });
44
-
45
- it('filters by kind', async () => {
46
- const output = await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --kind function`.text();
47
- const result = JSON.parse(output);
48
-
49
- expect(result.matched).toBe(3);
50
- expect(result.total).toBe(6);
51
- expect(result.spec.exports.every((e: { kind: string }) => e.kind === 'function')).toBe(true);
52
- });
53
-
54
- it('filters by multiple kinds', async () => {
55
- const output =
56
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --kind function,class`.text();
57
- const result = JSON.parse(output);
58
-
59
- expect(result.matched).toBe(5);
60
- });
61
-
62
- it('filters by name', async () => {
63
- const output =
64
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --name myFunction,MyClass`.text();
65
- const result = JSON.parse(output);
66
-
67
- expect(result.matched).toBe(2);
68
- });
69
-
70
- it('filters by deprecated flag', async () => {
71
- const output = await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --deprecated`.text();
72
- const result = JSON.parse(output);
73
-
74
- expect(result.matched).toBe(1);
75
- expect(result.spec.exports[0].name).toBe('deprecatedFn');
76
- });
77
-
78
- it('filters by non-deprecated', async () => {
79
- const output =
80
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --no-deprecated`.text();
81
- const result = JSON.parse(output);
82
-
83
- expect(result.matched).toBe(5);
84
- });
85
-
86
- it('filters by has-description', async () => {
87
- const output =
88
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --has-description`.text();
89
- const result = JSON.parse(output);
90
-
91
- expect(result.matched).toBe(4);
92
- });
93
-
94
- it('filters by missing-description', async () => {
95
- const output =
96
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --missing-description`.text();
97
- const result = JSON.parse(output);
98
-
99
- expect(result.matched).toBe(2);
100
- });
101
-
102
- it('filters by search term', async () => {
103
- const output = await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --search beta`.text();
104
- const result = JSON.parse(output);
105
-
106
- expect(result.matched).toBe(1);
107
- expect(result.spec.exports[0].name).toBe('taggedFn');
108
- });
109
-
110
- it('filters by tag', async () => {
111
- const output = await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --tag beta`.text();
112
- const result = JSON.parse(output);
113
-
114
- expect(result.matched).toBe(1);
115
- expect(result.spec.exports[0].name).toBe('taggedFn');
116
- });
117
-
118
- it('filters by module path', async () => {
119
- const output =
120
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --module src/utils`.text();
121
- const result = JSON.parse(output);
122
-
123
- expect(result.matched).toBe(1);
124
- expect(result.spec.exports[0].name).toBe('anotherClass');
125
- });
126
-
127
- it('combines filters with AND logic', async () => {
128
- const output =
129
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --kind function --has-description`.text();
130
- const result = JSON.parse(output);
131
-
132
- expect(result.matched).toBe(3);
133
- });
134
-
135
- it('outputs summary only with --summary', async () => {
136
- const output =
137
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --kind function --summary`.text();
138
- const result = JSON.parse(output);
139
-
140
- expect(result.matched).toBe(3);
141
- expect(result.total).toBe(6);
142
- expect(result.spec).toBeUndefined();
143
- });
144
-
145
- it('outputs raw spec with --quiet', async () => {
146
- const output =
147
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --kind function --quiet`.text();
148
- const result = JSON.parse(output);
149
-
150
- expect(result.openpkg).toBe('0.4.0');
151
- expect(result.exports.length).toBe(3);
152
- expect(result.matched).toBeUndefined();
153
- });
154
-
155
- it('writes to file with --output', async () => {
156
- const outPath = path.join(tmpDir, 'filtered.json');
157
- await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --kind class -o ${outPath}`.text();
158
-
159
- const content = JSON.parse(fs.readFileSync(outPath, 'utf-8'));
160
- expect(content.matched).toBe(2);
161
- });
162
-
163
- it('errors on invalid kind', async () => {
164
- const proc = await $`bun packages/cli/bin/openpkg.ts filter ${specPath} --kind invalid`
165
- .nothrow()
166
- .quiet();
167
- const result = JSON.parse(proc.stderr.toString());
168
-
169
- expect(result.error).toContain('Invalid kind(s): invalid');
170
- expect(result.error).toContain('Valid kinds:');
171
- });
172
-
173
- it('returns all exports with no criteria', async () => {
174
- const output = await $`bun packages/cli/bin/openpkg.ts filter ${specPath}`.text();
175
- const result = JSON.parse(output);
176
-
177
- expect(result.matched).toBe(6);
178
- expect(result.total).toBe(6);
179
- });
180
-
181
- it('handles empty spec', async () => {
182
- const emptyPath = path.join(tmpDir, 'empty.json');
183
- fs.writeFileSync(emptyPath, JSON.stringify({ openpkg: '0.4.0', meta: {}, exports: [] }));
184
-
185
- const output = await $`bun packages/cli/bin/openpkg.ts filter ${emptyPath} --kind function`.text();
186
- const result = JSON.parse(output);
187
-
188
- expect(result.matched).toBe(0);
189
- expect(result.total).toBe(0);
190
- });
191
-
192
- it('errors on malformed JSON', async () => {
193
- const badPath = path.join(tmpDir, 'bad.json');
194
- fs.writeFileSync(badPath, '{ invalid json }');
195
-
196
- const proc = await $`bun packages/cli/bin/openpkg.ts filter ${badPath}`.nothrow().quiet();
197
- const result = JSON.parse(proc.stderr.toString());
198
-
199
- expect(result.error).toBeDefined();
200
- });
201
-
202
- it('errors on missing file', async () => {
203
- const proc = await $`bun packages/cli/bin/openpkg.ts filter /nonexistent/path.json`
204
- .nothrow()
205
- .quiet();
206
- const result = JSON.parse(proc.stderr.toString());
207
-
208
- expect(result.error).toBeDefined();
209
- });
210
- });
@@ -1,131 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import { filterSpec, type FilterCriteria } from '@openpkg-ts/sdk';
4
- import type { OpenPkg, SpecExportKind } from '@openpkg-ts/spec';
5
- import { Command } from 'commander';
6
-
7
- const VALID_KINDS: SpecExportKind[] = [
8
- 'function',
9
- 'class',
10
- 'variable',
11
- 'interface',
12
- 'type',
13
- 'enum',
14
- 'module',
15
- 'namespace',
16
- 'reference',
17
- 'external',
18
- ];
19
-
20
- export type FilterResult = {
21
- spec: OpenPkg;
22
- matched: number;
23
- total: number;
24
- };
25
-
26
- export type FilterSummaryResult = {
27
- matched: number;
28
- total: number;
29
- };
30
-
31
- function loadSpec(filePath: string): OpenPkg {
32
- const resolved = path.resolve(filePath);
33
- const content = fs.readFileSync(resolved, 'utf-8');
34
- return JSON.parse(content) as OpenPkg;
35
- }
36
-
37
- function parseList(val?: string): string[] | undefined {
38
- if (!val) return undefined;
39
- return val
40
- .split(',')
41
- .map((s) => s.trim())
42
- .filter(Boolean);
43
- }
44
-
45
- function validateKinds(kinds: string[]): SpecExportKind[] {
46
- const invalid = kinds.filter((k) => !VALID_KINDS.includes(k as SpecExportKind));
47
- if (invalid.length > 0) {
48
- throw new Error(`Invalid kind(s): ${invalid.join(', ')}. Valid kinds: ${VALID_KINDS.join(', ')}`);
49
- }
50
- return kinds as SpecExportKind[];
51
- }
52
-
53
- export function createFilterCommand(): Command {
54
- return new Command('filter')
55
- .description('Filter an OpenPkg spec by various criteria')
56
- .argument('<spec>', 'Path to spec file (JSON)')
57
- .option('--kind <kinds>', 'Filter by kinds (comma-separated)')
58
- .option('--name <names>', 'Filter by exact names (comma-separated)')
59
- .option('--id <ids>', 'Filter by IDs (comma-separated)')
60
- .option('--tag <tags>', 'Filter by tags (comma-separated)')
61
- .option('--deprecated', 'Only deprecated exports')
62
- .option('--no-deprecated', 'Exclude deprecated exports')
63
- .option('--has-description', 'Only exports with descriptions')
64
- .option('--missing-description', 'Only exports without descriptions')
65
- .option('--search <term>', 'Search name/description (case-insensitive)')
66
- .option('--module <path>', 'Filter by source file path (contains)')
67
- .option('-o, --output <file>', 'Output file (default: stdout)')
68
- .option('--summary', 'Only output matched/total counts')
69
- .option('--quiet', 'Output raw spec only (no wrapper)')
70
- .action(
71
- async (
72
- specPath: string,
73
- options: {
74
- kind?: string;
75
- name?: string;
76
- id?: string;
77
- tag?: string;
78
- deprecated?: boolean;
79
- hasDescription?: boolean;
80
- missingDescription?: boolean;
81
- search?: string;
82
- module?: string;
83
- output?: string;
84
- summary?: boolean;
85
- quiet?: boolean;
86
- }
87
- ) => {
88
- try {
89
- const spec = loadSpec(specPath);
90
-
91
- const criteria: FilterCriteria = {};
92
-
93
- if (options.kind) {
94
- const kinds = parseList(options.kind);
95
- if (kinds) criteria.kinds = validateKinds(kinds);
96
- }
97
- if (options.name) criteria.names = parseList(options.name);
98
- if (options.id) criteria.ids = parseList(options.id);
99
- if (options.tag) criteria.tags = parseList(options.tag);
100
- if (options.deprecated !== undefined) criteria.deprecated = options.deprecated;
101
- if (options.hasDescription) criteria.hasDescription = true;
102
- if (options.missingDescription) criteria.hasDescription = false;
103
- if (options.search) criteria.search = options.search;
104
- if (options.module) criteria.module = options.module;
105
-
106
- const result = filterSpec(spec, criteria);
107
-
108
- let output: OpenPkg | FilterResult | FilterSummaryResult;
109
- if (options.summary) {
110
- output = { matched: result.matched, total: result.total };
111
- } else if (options.quiet) {
112
- output = result.spec;
113
- } else {
114
- output = { spec: result.spec, matched: result.matched, total: result.total };
115
- }
116
-
117
- const json = JSON.stringify(output, null, 2);
118
-
119
- if (options.output) {
120
- fs.writeFileSync(path.resolve(options.output), json);
121
- } else {
122
- console.log(json);
123
- }
124
- } catch (err) {
125
- const error = err instanceof Error ? err : new Error(String(err));
126
- console.error(JSON.stringify({ error: error.message }, null, 2));
127
- process.exit(1);
128
- }
129
- }
130
- );
131
- }
@@ -1,43 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
- import { diffSpec, type OpenPkg, recommendSemverBump, type SemverBump } from '@openpkg-ts/spec';
4
- import { Command } from 'commander';
5
-
6
- export type SemverResult = {
7
- bump: SemverBump;
8
- reason: string;
9
- };
10
-
11
- function loadSpec(filePath: string): OpenPkg {
12
- const resolved = path.resolve(filePath);
13
- const content = fs.readFileSync(resolved, 'utf-8');
14
- return JSON.parse(content) as OpenPkg;
15
- }
16
-
17
- export function createSemverCommand(): Command {
18
- return new Command('semver')
19
- .description('Recommend semver bump based on spec changes')
20
- .argument('<old>', 'Path to old spec file (JSON)')
21
- .argument('<new>', 'Path to new spec file (JSON)')
22
- .action(async (oldPath: string, newPath: string) => {
23
- try {
24
- const oldSpec = loadSpec(oldPath);
25
- const newSpec = loadSpec(newPath);
26
-
27
- const diff = diffSpec(oldSpec, newSpec);
28
- const recommendation = recommendSemverBump(diff);
29
-
30
- const result: SemverResult = {
31
- bump: recommendation.bump,
32
- reason: recommendation.reason,
33
- };
34
-
35
- console.log(JSON.stringify(result, null, 2));
36
- // Always exit 0 - this is a recommendation only
37
- } catch (err) {
38
- const error = err instanceof Error ? err : new Error(String(err));
39
- console.error(JSON.stringify({ error: error.message }, null, 2));
40
- process.exit(1);
41
- }
42
- });
43
- }