@openpkg-ts/cli 0.3.0 → 0.4.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.
@@ -1,189 +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 type { OpenPkg } from '@openpkg-ts/spec';
6
- import { $ } from 'bun';
7
-
8
- /**
9
- * E2E test for full release workflow:
10
- * old.json → new.json → breaking check → semver → changelog → docs (fumadocs)
11
- */
12
- describe('release workflow e2e', () => {
13
- let tmpDir: string;
14
- let oldPath: string;
15
- let newPath: string;
16
-
17
- const oldSpec: OpenPkg = {
18
- openpkg: '0.4.0',
19
- meta: { name: 'test-pkg', version: '1.0.0' },
20
- exports: [
21
- {
22
- id: 'fn-greet',
23
- name: 'greet',
24
- kind: 'function',
25
- description: 'Greets a person',
26
- signatures: [
27
- {
28
- parameters: [
29
- { name: 'name', schema: { type: 'string' }, description: 'Person name' },
30
- ],
31
- returns: { schema: { type: 'string' }, description: 'Greeting message' },
32
- },
33
- ],
34
- },
35
- {
36
- id: 'fn-goodbye',
37
- name: 'goodbye',
38
- kind: 'function',
39
- description: 'Says goodbye',
40
- signatures: [],
41
- },
42
- ],
43
- types: [],
44
- };
45
-
46
- const newSpec: OpenPkg = {
47
- openpkg: '0.4.0',
48
- meta: { name: 'test-pkg', version: '2.0.0' },
49
- exports: [
50
- {
51
- id: 'fn-greet',
52
- name: 'greet',
53
- kind: 'function',
54
- description: 'Greets a person with optional message',
55
- signatures: [
56
- {
57
- parameters: [
58
- { name: 'name', schema: { type: 'string' }, description: 'Person name' },
59
- { name: 'message', schema: { type: 'string' }, description: 'Optional message' },
60
- ],
61
- returns: { schema: { type: 'string' }, description: 'Greeting message' },
62
- },
63
- ],
64
- },
65
- {
66
- id: 'fn-hello',
67
- name: 'hello',
68
- kind: 'function',
69
- description: 'New hello function',
70
- signatures: [],
71
- },
72
- ],
73
- types: [],
74
- };
75
-
76
- beforeAll(() => {
77
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'release-workflow-test-'));
78
- oldPath = path.join(tmpDir, 'old.json');
79
- newPath = path.join(tmpDir, 'new.json');
80
-
81
- fs.writeFileSync(oldPath, JSON.stringify(oldSpec));
82
- fs.writeFileSync(newPath, JSON.stringify(newSpec));
83
- });
84
-
85
- afterAll(() => {
86
- fs.rmSync(tmpDir, { recursive: true });
87
- });
88
-
89
- it('detects breaking changes', async () => {
90
- const proc = Bun.spawn(['bun', 'packages/cli/bin/openpkg.ts', 'breaking', oldPath, newPath]);
91
- const stdout = await new Response(proc.stdout).text();
92
- const exitCode = await proc.exited;
93
-
94
- const result = JSON.parse(stdout);
95
-
96
- // Should find breaking change: goodbye removed
97
- expect(result.count).toBeGreaterThan(0);
98
- expect(exitCode).toBe(1); // exits 1 when breaking changes found
99
- });
100
-
101
- it('recommends semver bump', async () => {
102
- const output = await $`bun packages/cli/bin/openpkg.ts semver ${oldPath} ${newPath}`.text();
103
- const result = JSON.parse(output);
104
-
105
- expect(result.bump).toBe('major'); // breaking change = major
106
- expect(result.reason).toBeDefined();
107
- });
108
-
109
- it('generates changelog', async () => {
110
- const output = await $`bun packages/cli/bin/openpkg.ts changelog ${oldPath} ${newPath} --format json`.text();
111
- const result = JSON.parse(output);
112
-
113
- expect(result.breaking).toBeDefined();
114
- expect(result.added).toBeDefined();
115
- expect(result.removed).toBeDefined();
116
- expect(result.summary).toBeDefined();
117
-
118
- // Verify removed function detected (removed has full objects)
119
- const removed = result.removed.find((r: { name: string }) => r.name === 'goodbye');
120
- expect(removed).toBeDefined();
121
-
122
- // Verify added function detected (added is array of IDs)
123
- expect(result.added).toContain('fn-hello');
124
- });
125
-
126
- it('generates markdown docs', async () => {
127
- const output = await $`bun packages/cli/bin/openpkg.ts docs ${newPath}`.text();
128
-
129
- expect(output).toContain('greet');
130
- expect(output).toContain('hello');
131
- expect(output).toContain('Greets a person');
132
- });
133
-
134
- it('generates json docs', async () => {
135
- const output = await $`bun packages/cli/bin/openpkg.ts docs ${newPath} --format json`.text();
136
- const result = JSON.parse(output);
137
-
138
- expect(result.name).toBe('test-pkg');
139
- expect(result.exports).toBeDefined();
140
- expect(result.exports.length).toBe(2);
141
- });
142
-
143
- it('generates fumadocs output', async () => {
144
- const docsDir = path.join(tmpDir, 'fumadocs-output');
145
-
146
- await $`bun packages/cli/bin/openpkg.ts docs ${newPath} --adapter fumadocs --output ${docsDir}`;
147
-
148
- // Verify directory created
149
- expect(fs.existsSync(docsDir)).toBe(true);
150
-
151
- // Verify markdown files created (one per export)
152
- const files = fs.readdirSync(docsDir);
153
- const mdFiles = files.filter((f) => f.endsWith('.md'));
154
- expect(mdFiles.length).toBe(2); // greet.md, hello.md
155
- });
156
-
157
- it('validates new spec', async () => {
158
- const output = await $`bun packages/cli/bin/openpkg.ts validate ${newPath}`.text();
159
- const result = JSON.parse(output);
160
-
161
- expect(result.valid).toBe(true);
162
- expect(result.errors).toHaveLength(0);
163
- });
164
-
165
- it('runs diagnostics on new spec', async () => {
166
- const output = await $`bun packages/cli/bin/openpkg.ts diagnostics ${newPath}`.text();
167
- const result = JSON.parse(output);
168
-
169
- expect(result.summary).toBeDefined();
170
- expect(result.diagnostics).toBeDefined();
171
- });
172
-
173
- it('full workflow runs without error', async () => {
174
- // Simulate complete release workflow sequence
175
- const steps = [
176
- $`bun packages/cli/bin/openpkg.ts validate ${oldPath}`,
177
- $`bun packages/cli/bin/openpkg.ts validate ${newPath}`,
178
- $`bun packages/cli/bin/openpkg.ts semver ${oldPath} ${newPath}`,
179
- $`bun packages/cli/bin/openpkg.ts changelog ${oldPath} ${newPath} --format json`,
180
- $`bun packages/cli/bin/openpkg.ts diagnostics ${newPath}`,
181
- $`bun packages/cli/bin/openpkg.ts docs ${newPath} --format json`,
182
- ];
183
-
184
- for (const step of steps) {
185
- const output = await step.text();
186
- expect(() => JSON.parse(output)).not.toThrow();
187
- }
188
- });
189
- });
package/test/get.test.ts DELETED
@@ -1,413 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { getExport } from '@openpkg-ts/sdk';
3
-
4
- describe('getExport', () => {
5
- describe('function exports', () => {
6
- test('gets function with signature and parameters', async () => {
7
- const code = `
8
- /** Creates a new client */
9
- export function createClient(config: Config): Client {
10
- return {} as Client;
11
- }
12
- interface Config { baseUrl: string; }
13
- interface Client { fetch(): void; }
14
- `;
15
-
16
- const result = await getExport({
17
- entryFile: 'test.ts',
18
- exportName: 'createClient',
19
- content: code,
20
- });
21
-
22
- expect(result.errors).toHaveLength(0);
23
- expect(result.export).not.toBeNull();
24
- expect(result.export!.kind).toBe('function');
25
- expect(result.export!.signatures).toBeDefined();
26
- expect(result.export!.signatures).toHaveLength(1);
27
-
28
- const sig = result.export!.signatures![0];
29
- expect(sig.parameters).toHaveLength(1);
30
- expect(sig.parameters![0].name).toBe('config');
31
- });
32
-
33
- test('gets arrow function export', async () => {
34
- const code = `
35
- /** Arrow function export */
36
- export const add = (a: number, b: number): number => a + b;
37
- `;
38
-
39
- const result = await getExport({ entryFile: 'test.ts', exportName: 'add', content: code });
40
-
41
- expect(result.errors).toHaveLength(0);
42
- expect(result.export).not.toBeNull();
43
- // Arrow functions assigned to const are classified as variables at declaration level
44
- // but listExports detects them as functions via type analysis
45
- expect(['function', 'variable']).toContain(result.export!.kind);
46
- });
47
-
48
- test('gets function with overloads', async () => {
49
- const code = `
50
- /** @overload */
51
- export function parse(input: string): object;
52
- /** @overload */
53
- export function parse(input: Buffer): object;
54
- export function parse(input: string | Buffer): object {
55
- return {};
56
- }
57
- `;
58
-
59
- const result = await getExport({ entryFile: 'test.ts', exportName: 'parse', content: code });
60
-
61
- expect(result.errors).toHaveLength(0);
62
- expect(result.export!.kind).toBe('function');
63
- // Should have multiple signatures for overloads
64
- expect(result.export!.signatures!.length).toBeGreaterThanOrEqual(1);
65
- });
66
- });
67
-
68
- describe('interface exports', () => {
69
- test('gets interface with properties', async () => {
70
- const code = `
71
- /** Configuration interface */
72
- export interface Config {
73
- /** Base URL for API */
74
- baseUrl: string;
75
- /** Optional timeout */
76
- timeout?: number;
77
- }
78
- `;
79
-
80
- const result = await getExport({ entryFile: 'test.ts', exportName: 'Config', content: code });
81
-
82
- expect(result.errors).toHaveLength(0);
83
- expect(result.export).not.toBeNull();
84
- expect(result.export!.kind).toBe('interface');
85
- expect(result.export!.members).toBeDefined();
86
- expect(result.export!.members!.length).toBe(2);
87
-
88
- const baseUrl = result.export!.members!.find((m: { name: string }) => m.name === 'baseUrl');
89
- expect(baseUrl).toBeDefined();
90
- });
91
-
92
- test('gets interface with methods', async () => {
93
- const code = `
94
- /** Client interface */
95
- export interface Client {
96
- /** Fetch data */
97
- fetch(url: string): Promise<Response>;
98
- }
99
- `;
100
-
101
- const result = await getExport({ entryFile: 'test.ts', exportName: 'Client', content: code });
102
-
103
- expect(result.errors).toHaveLength(0);
104
- expect(result.export!.kind).toBe('interface');
105
- expect(result.export!.members).toBeDefined();
106
-
107
- const fetch = result.export!.members!.find((m: { name: string }) => m.name === 'fetch');
108
- expect(fetch).toBeDefined();
109
- });
110
- });
111
-
112
- describe('type alias exports', () => {
113
- test('gets type alias', async () => {
114
- const code = `
115
- /** Union type */
116
- export type Result = Success | Error;
117
- interface Success { ok: true; data: unknown; }
118
- interface Error { ok: false; error: string; }
119
- `;
120
-
121
- const result = await getExport({ entryFile: 'test.ts', exportName: 'Result', content: code });
122
-
123
- expect(result.errors).toHaveLength(0);
124
- expect(result.export).not.toBeNull();
125
- expect(result.export!.kind).toBe('type');
126
- });
127
-
128
- test('gets mapped type', async () => {
129
- const code = `
130
- /** Make all properties optional */
131
- export type Partial<T> = { [K in keyof T]?: T[K] };
132
- `;
133
-
134
- const result = await getExport({
135
- entryFile: 'test.ts',
136
- exportName: 'Partial',
137
- content: code,
138
- });
139
-
140
- expect(result.errors).toHaveLength(0);
141
- expect(result.export!.kind).toBe('type');
142
- });
143
- });
144
-
145
- describe('class exports', () => {
146
- test('gets class with members', async () => {
147
- const code = `
148
- /** API Client class */
149
- export class ApiClient {
150
- /** Base URL */
151
- baseUrl: string;
152
-
153
- constructor(config: { baseUrl: string }) {
154
- this.baseUrl = config.baseUrl;
155
- }
156
-
157
- /** Make request */
158
- async request(path: string): Promise<Response> {
159
- return fetch(this.baseUrl + path);
160
- }
161
- }
162
- `;
163
-
164
- const result = await getExport({
165
- entryFile: 'test.ts',
166
- exportName: 'ApiClient',
167
- content: code,
168
- });
169
-
170
- expect(result.errors).toHaveLength(0);
171
- expect(result.export).not.toBeNull();
172
- expect(result.export!.kind).toBe('class');
173
- expect(result.export!.members).toBeDefined();
174
-
175
- // Should have baseUrl property and request method
176
- const members = result.export!.members!;
177
- expect(members.some((m: { name: string }) => m.name === 'baseUrl')).toBe(true);
178
- expect(members.some((m: { name: string }) => m.name === 'request')).toBe(true);
179
- });
180
-
181
- test('gets class with static members', async () => {
182
- const code = `
183
- /** Class with statics */
184
- export class Utils {
185
- static readonly VERSION = '1.0.0';
186
- static format(s: string): string { return s; }
187
- }
188
- `;
189
-
190
- const result = await getExport({ entryFile: 'test.ts', exportName: 'Utils', content: code });
191
-
192
- expect(result.errors).toHaveLength(0);
193
- expect(result.export!.kind).toBe('class');
194
-
195
- const members = result.export!.members!;
196
- const version = members.find((m: { name: string }) => m.name === 'VERSION');
197
- const format = members.find((m: { name: string }) => m.name === 'format');
198
- expect(version).toBeDefined();
199
- expect(format).toBeDefined();
200
- });
201
- });
202
-
203
- describe('enum exports', () => {
204
- test('gets enum with members', async () => {
205
- const code = `
206
- /** Log levels */
207
- export enum LogLevel {
208
- DEBUG = 0,
209
- INFO = 1,
210
- WARN = 2,
211
- ERROR = 3
212
- }
213
- `;
214
-
215
- const result = await getExport({
216
- entryFile: 'test.ts',
217
- exportName: 'LogLevel',
218
- content: code,
219
- });
220
-
221
- expect(result.errors).toHaveLength(0);
222
- expect(result.export).not.toBeNull();
223
- expect(result.export!.kind).toBe('enum');
224
- expect(result.export!.members).toBeDefined();
225
- expect(result.export!.members!.length).toBe(4);
226
- });
227
-
228
- test('gets string enum', async () => {
229
- const code = `
230
- /** Status values */
231
- export enum Status {
232
- Pending = 'pending',
233
- Active = 'active',
234
- Done = 'done'
235
- }
236
- `;
237
-
238
- const result = await getExport({ entryFile: 'test.ts', exportName: 'Status', content: code });
239
-
240
- expect(result.errors).toHaveLength(0);
241
- expect(result.export!.kind).toBe('enum');
242
- expect(result.export!.members!.length).toBe(3);
243
- });
244
- });
245
-
246
- describe('variable exports', () => {
247
- test('gets const variable', async () => {
248
- const code = `
249
- /** Default config */
250
- export const DEFAULT_CONFIG = { timeout: 5000, retries: 3 };
251
- `;
252
-
253
- const result = await getExport({
254
- entryFile: 'test.ts',
255
- exportName: 'DEFAULT_CONFIG',
256
- content: code,
257
- });
258
-
259
- expect(result.errors).toHaveLength(0);
260
- expect(result.export).not.toBeNull();
261
- expect(result.export!.kind).toBe('variable');
262
- });
263
-
264
- test('gets typed const', async () => {
265
- const code = `
266
- /** API version */
267
- export const VERSION: string = '1.0.0';
268
- `;
269
-
270
- const result = await getExport({
271
- entryFile: 'test.ts',
272
- exportName: 'VERSION',
273
- content: code,
274
- });
275
-
276
- expect(result.errors).toHaveLength(0);
277
- expect(result.export!.kind).toBe('variable');
278
- });
279
- });
280
-
281
- describe('namespace exports', () => {
282
- test('gets namespace with exports', async () => {
283
- const code = `
284
- /** Utility namespace */
285
- export namespace Utils {
286
- export const PI = 3.14159;
287
- export function square(n: number): number { return n * n; }
288
- }
289
- `;
290
-
291
- const result = await getExport({ entryFile: 'test.ts', exportName: 'Utils', content: code });
292
-
293
- // Namespace serialization may not be fully implemented
294
- // Check that we at least get a result or a meaningful error
295
- if (result.export) {
296
- expect(result.export.kind).toBe('namespace');
297
- } else {
298
- // If namespace not supported, we expect a serialization error
299
- expect(result.errors.length).toBeGreaterThan(0);
300
- }
301
- });
302
- });
303
-
304
- describe('error handling', () => {
305
- test('returns error for non-existent export', async () => {
306
- const code = `
307
- export function myFunc(): void {}
308
- `;
309
-
310
- const result = await getExport({
311
- entryFile: 'test.ts',
312
- exportName: 'NonExistent',
313
- content: code,
314
- });
315
-
316
- expect(result.export).toBeNull();
317
- expect(result.errors.length).toBeGreaterThan(0);
318
- expect(result.errors[0]).toContain("Export 'NonExistent' not found");
319
- });
320
-
321
- test('returns error for empty file', async () => {
322
- const code = `// empty file`;
323
-
324
- const result = await getExport({
325
- entryFile: 'test.ts',
326
- exportName: 'anything',
327
- content: code,
328
- });
329
-
330
- expect(result.export).toBeNull();
331
- expect(result.errors.length).toBeGreaterThan(0);
332
- });
333
- });
334
-
335
- describe('related types', () => {
336
- test('includes referenced types', async () => {
337
- const code = `
338
- export interface Response {
339
- data: Data;
340
- }
341
- export interface Data {
342
- id: string;
343
- }
344
- `;
345
-
346
- const result = await getExport({
347
- entryFile: 'test.ts',
348
- exportName: 'Response',
349
- content: code,
350
- });
351
-
352
- expect(result.errors).toHaveLength(0);
353
- expect(result.export).not.toBeNull();
354
- // Related types may be in result.types
355
- // Depends on implementation - Data may or may not be included
356
- });
357
- });
358
-
359
- describe('output structure', () => {
360
- test('output matches spec format', async () => {
361
- const code = `
362
- /** Creates something */
363
- export function create(name: string): void {}
364
- `;
365
-
366
- const result = await getExport({ entryFile: 'test.ts', exportName: 'create', content: code });
367
-
368
- expect(result.export).not.toBeNull();
369
-
370
- // Verify spec structure
371
- const exp = result.export!;
372
- expect(exp.kind).toBe('function');
373
- expect(exp.name).toBe('create');
374
- expect(typeof exp.description).toBe('string');
375
- });
376
-
377
- test('result has export, types, errors fields', async () => {
378
- const code = `export const x = 1;`;
379
-
380
- const result = await getExport({ entryFile: 'test.ts', exportName: 'x', content: code });
381
-
382
- expect(result).toHaveProperty('export');
383
- expect(result).toHaveProperty('types');
384
- expect(result).toHaveProperty('errors');
385
- expect(Array.isArray(result.types)).toBe(true);
386
- expect(Array.isArray(result.errors)).toBe(true);
387
- });
388
- });
389
-
390
- describe('performance', () => {
391
- test('single export lookup is reasonably fast', async () => {
392
- const code = `
393
- export function myFunc(): void {}
394
- export interface MyInterface { value: string; }
395
- export class MyClass { prop: number = 0; }
396
- export type MyType = string | number;
397
- export const myVar = 42;
398
- export enum MyEnum { A, B, C }
399
- `;
400
-
401
- // Each call creates a new TypeScript program, so overhead is expected
402
- // Target: under 2s for cold start, which is acceptable for CLI usage
403
- const start = performance.now();
404
- const result = await getExport({ entryFile: 'test.ts', exportName: 'myFunc', content: code });
405
- const elapsed = performance.now() - start;
406
-
407
- expect(result.errors).toHaveLength(0);
408
- expect(result.export).not.toBeNull();
409
- // 2s is generous but accounts for CI variance
410
- expect(elapsed).toBeLessThan(2000);
411
- });
412
- });
413
- });