@openpkg-ts/cli 0.2.3 → 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.
package/test/spec.test.ts DELETED
@@ -1,469 +0,0 @@
1
- import { afterAll, beforeAll, 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 { extractSpec } from '@openpkg-ts/sdk';
6
-
7
- // Test fixture code samples
8
- const FIXTURE_CODE = {
9
- basic: `
10
- /** Creates a new client */
11
- export function createClient(config: Config): Client {
12
- return {} as Client;
13
- }
14
- /** Configuration interface */
15
- export interface Config {
16
- baseUrl: string;
17
- timeout?: number;
18
- }
19
- interface Client { fetch(): void; }
20
- /** API version */
21
- export const VERSION = '1.0.0';
22
- `,
23
- withClasses: `
24
- /** Base class */
25
- export class Service {
26
- name: string = 'service';
27
- start(): void {}
28
- }
29
- /** Helper type */
30
- export type ServiceConfig = { enabled: boolean };
31
- `,
32
- withEnums: `
33
- /** Log levels */
34
- export enum LogLevel {
35
- DEBUG = 0,
36
- INFO = 1,
37
- ERROR = 2
38
- }
39
- /** Status values */
40
- export enum Status {
41
- Pending = 'pending',
42
- Done = 'done'
43
- }
44
- `,
45
- forFiltering: `
46
- export function createUser() {}
47
- export function createPost() {}
48
- export function deleteUser() {}
49
- export const MAX_USERS = 100;
50
- export interface User { id: string }
51
- `,
52
- empty: `// empty file`,
53
- syntaxError: `export function broken( { syntax }`,
54
- };
55
-
56
- describe('spec command (extractSpec)', () => {
57
- let tmpDir: string;
58
-
59
- beforeAll(() => {
60
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openpkg-spec-test-'));
61
- });
62
-
63
- afterAll(() => {
64
- fs.rmSync(tmpDir, { recursive: true, force: true });
65
- });
66
-
67
- describe('valid spec generation', () => {
68
- test('generates spec with exports', async () => {
69
- const result = await extractSpec({
70
- entryFile: 'test.ts',
71
- content: FIXTURE_CODE.basic,
72
- });
73
-
74
- expect(result.spec).toBeDefined();
75
- expect(result.spec.openpkg).toBeDefined();
76
- expect(result.spec.exports.length).toBeGreaterThan(0);
77
- expect(result.diagnostics).toBeInstanceOf(Array);
78
- });
79
-
80
- test('includes meta information', async () => {
81
- const result = await extractSpec({
82
- entryFile: 'mypackage/index.ts',
83
- content: FIXTURE_CODE.basic,
84
- });
85
-
86
- expect(result.spec.meta).toBeDefined();
87
- });
88
-
89
- test('extracts all export kinds', async () => {
90
- const result = await extractSpec({
91
- entryFile: 'test.ts',
92
- content: FIXTURE_CODE.basic,
93
- });
94
-
95
- const kinds = new Set(result.spec.exports.map((e) => e.kind));
96
- expect(kinds.has('function')).toBe(true);
97
- expect(kinds.has('interface')).toBe(true);
98
- expect(kinds.has('variable')).toBe(true);
99
- });
100
-
101
- test('extracts class exports', async () => {
102
- const result = await extractSpec({
103
- entryFile: 'test.ts',
104
- content: FIXTURE_CODE.withClasses,
105
- });
106
-
107
- const classExport = result.spec.exports.find((e) => e.kind === 'class');
108
- expect(classExport).toBeDefined();
109
- expect(classExport!.name).toBe('Service');
110
- });
111
-
112
- test('extracts enum exports', async () => {
113
- const result = await extractSpec({
114
- entryFile: 'test.ts',
115
- content: FIXTURE_CODE.withEnums,
116
- });
117
-
118
- const enums = result.spec.exports.filter((e) => e.kind === 'enum');
119
- expect(enums.length).toBe(2);
120
- });
121
-
122
- test('includes types array', async () => {
123
- const result = await extractSpec({
124
- entryFile: 'test.ts',
125
- content: FIXTURE_CODE.basic,
126
- });
127
-
128
- expect(result.spec.types).toBeInstanceOf(Array);
129
- });
130
- });
131
-
132
- describe('output modes', () => {
133
- test('extractSpec returns result object directly', async () => {
134
- const result = await extractSpec({
135
- entryFile: 'test.ts',
136
- content: FIXTURE_CODE.basic,
137
- });
138
-
139
- // SDK returns result directly - CLI handles stdout/file writing
140
- expect(result.spec).toBeDefined();
141
- expect(result.diagnostics).toBeDefined();
142
- });
143
-
144
- test('spec can be serialized to JSON', async () => {
145
- const result = await extractSpec({
146
- entryFile: 'test.ts',
147
- content: FIXTURE_CODE.basic,
148
- });
149
-
150
- const json = JSON.stringify(result.spec, null, 2);
151
- const parsed = JSON.parse(json);
152
-
153
- expect(parsed.openpkg).toBeDefined();
154
- expect(parsed.exports).toBeInstanceOf(Array);
155
- });
156
-
157
- test('spec output is deterministic', async () => {
158
- const result1 = await extractSpec({
159
- entryFile: 'test.ts',
160
- content: FIXTURE_CODE.basic,
161
- });
162
- const result2 = await extractSpec({
163
- entryFile: 'test.ts',
164
- content: FIXTURE_CODE.basic,
165
- });
166
-
167
- // Export names and kinds should be consistent
168
- const names1 = result1.spec.exports.map((e) => e.name).sort();
169
- const names2 = result2.spec.exports.map((e) => e.name).sort();
170
- expect(names1).toEqual(names2);
171
- });
172
- });
173
-
174
- describe('filtering with --only', () => {
175
- test('filters to exact match', async () => {
176
- const result = await extractSpec({
177
- entryFile: 'test.ts',
178
- content: FIXTURE_CODE.forFiltering,
179
- only: ['createUser'],
180
- });
181
-
182
- expect(result.spec.exports.length).toBe(1);
183
- expect(result.spec.exports[0].name).toBe('createUser');
184
- });
185
-
186
- test('filters with wildcard prefix', async () => {
187
- const result = await extractSpec({
188
- entryFile: 'test.ts',
189
- content: FIXTURE_CODE.forFiltering,
190
- only: ['create*'],
191
- });
192
-
193
- expect(result.spec.exports.length).toBe(2);
194
- expect(result.spec.exports.every((e) => e.name.startsWith('create'))).toBe(true);
195
- });
196
-
197
- test('filters with wildcard suffix', async () => {
198
- const result = await extractSpec({
199
- entryFile: 'test.ts',
200
- content: FIXTURE_CODE.forFiltering,
201
- only: ['*User'],
202
- });
203
-
204
- const names = result.spec.exports.map((e) => e.name);
205
- expect(names.every((n) => n.endsWith('User'))).toBe(true);
206
- });
207
-
208
- test('filters multiple patterns', async () => {
209
- const result = await extractSpec({
210
- entryFile: 'test.ts',
211
- content: FIXTURE_CODE.forFiltering,
212
- only: ['createUser', 'MAX_USERS'],
213
- });
214
-
215
- expect(result.spec.exports.length).toBe(2);
216
- const names = result.spec.exports.map((e) => e.name);
217
- expect(names).toContain('createUser');
218
- expect(names).toContain('MAX_USERS');
219
- });
220
-
221
- test('returns empty exports when no matches', async () => {
222
- const result = await extractSpec({
223
- entryFile: 'test.ts',
224
- content: FIXTURE_CODE.forFiltering,
225
- only: ['nonExistent'],
226
- });
227
-
228
- expect(result.spec.exports.length).toBe(0);
229
- });
230
- });
231
-
232
- describe('filtering with --ignore', () => {
233
- test('ignores exact match', async () => {
234
- const result = await extractSpec({
235
- entryFile: 'test.ts',
236
- content: FIXTURE_CODE.forFiltering,
237
- ignore: ['deleteUser'],
238
- });
239
-
240
- const names = result.spec.exports.map((e) => e.name);
241
- expect(names).not.toContain('deleteUser');
242
- });
243
-
244
- test('ignores with wildcard', async () => {
245
- const result = await extractSpec({
246
- entryFile: 'test.ts',
247
- content: FIXTURE_CODE.forFiltering,
248
- ignore: ['*User'],
249
- });
250
-
251
- const names = result.spec.exports.map((e) => e.name);
252
- expect(names.some((n) => n.endsWith('User'))).toBe(false);
253
- });
254
-
255
- test('ignores multiple patterns', async () => {
256
- const result = await extractSpec({
257
- entryFile: 'test.ts',
258
- content: FIXTURE_CODE.forFiltering,
259
- ignore: ['deleteUser', 'MAX_USERS'],
260
- });
261
-
262
- const names = result.spec.exports.map((e) => e.name);
263
- expect(names).not.toContain('deleteUser');
264
- expect(names).not.toContain('MAX_USERS');
265
- });
266
-
267
- test('only and ignore can combine', async () => {
268
- const result = await extractSpec({
269
- entryFile: 'test.ts',
270
- content: FIXTURE_CODE.forFiltering,
271
- only: ['create*'],
272
- ignore: ['createPost'],
273
- });
274
-
275
- expect(result.spec.exports.length).toBe(1);
276
- expect(result.spec.exports[0].name).toBe('createUser');
277
- });
278
- });
279
-
280
- describe('verification', () => {
281
- test('includes verification stats', async () => {
282
- const result = await extractSpec({
283
- entryFile: 'test.ts',
284
- content: FIXTURE_CODE.basic,
285
- });
286
-
287
- // Verification is always included
288
- expect(result.verification).toBeDefined();
289
- expect(typeof result.verification!.discovered).toBe('number');
290
- expect(typeof result.verification!.extracted).toBe('number');
291
- expect(typeof result.verification!.failed).toBe('number');
292
- });
293
-
294
- test('verification tracks filtered exports', async () => {
295
- const result = await extractSpec({
296
- entryFile: 'test.ts',
297
- content: FIXTURE_CODE.forFiltering,
298
- only: ['createUser'],
299
- });
300
-
301
- // Some exports should be skipped due to filter
302
- expect(result.verification!.skipped).toBeGreaterThan(0);
303
- });
304
-
305
- test('verification details include skip reasons', async () => {
306
- const result = await extractSpec({
307
- entryFile: 'test.ts',
308
- content: FIXTURE_CODE.forFiltering,
309
- only: ['createUser'],
310
- });
311
-
312
- if (result.verification!.skipped > 0) {
313
- expect(result.verification!.details.skipped.length).toBeGreaterThan(0);
314
- expect(result.verification!.details.skipped[0].reason).toBe('filtered');
315
- }
316
- });
317
- });
318
-
319
- describe('error handling', () => {
320
- test('handles empty file gracefully', async () => {
321
- const result = await extractSpec({
322
- entryFile: 'test.ts',
323
- content: FIXTURE_CODE.empty,
324
- });
325
-
326
- expect(result.spec.exports.length).toBe(0);
327
- // Should not throw
328
- });
329
-
330
- test('returns diagnostics for issues', async () => {
331
- const result = await extractSpec({
332
- entryFile: 'test.ts',
333
- content: FIXTURE_CODE.basic,
334
- });
335
-
336
- expect(result.diagnostics).toBeInstanceOf(Array);
337
- // Diagnostics may be empty for valid code
338
- });
339
-
340
- test('diagnostics have correct structure', async () => {
341
- const result = await extractSpec({
342
- entryFile: 'test.ts',
343
- content: FIXTURE_CODE.basic,
344
- });
345
-
346
- for (const diag of result.diagnostics) {
347
- expect(typeof diag.message).toBe('string');
348
- expect(['error', 'warning', 'info']).toContain(diag.severity);
349
- }
350
- });
351
- });
352
-
353
- describe('output structure', () => {
354
- test('spec has openpkg version', async () => {
355
- const result = await extractSpec({
356
- entryFile: 'test.ts',
357
- content: FIXTURE_CODE.basic,
358
- });
359
-
360
- expect(result.spec.openpkg).toMatch(/^\d+\.\d+\.\d+/);
361
- });
362
-
363
- test('spec has meta object', async () => {
364
- const result = await extractSpec({
365
- entryFile: 'test.ts',
366
- content: FIXTURE_CODE.basic,
367
- });
368
-
369
- expect(result.spec.meta).toBeDefined();
370
- expect(typeof result.spec.meta).toBe('object');
371
- });
372
-
373
- test('exports have required fields', async () => {
374
- const result = await extractSpec({
375
- entryFile: 'test.ts',
376
- content: FIXTURE_CODE.basic,
377
- });
378
-
379
- for (const exp of result.spec.exports) {
380
- expect(exp.id).toBeDefined();
381
- expect(exp.name).toBeDefined();
382
- expect(exp.kind).toBeDefined();
383
- }
384
- });
385
-
386
- test('function exports have signatures', async () => {
387
- const result = await extractSpec({
388
- entryFile: 'test.ts',
389
- content: FIXTURE_CODE.basic,
390
- });
391
-
392
- const fn = result.spec.exports.find((e) => e.kind === 'function');
393
- expect(fn).toBeDefined();
394
- expect(fn!.signatures).toBeDefined();
395
- });
396
-
397
- test('interface exports have members', async () => {
398
- const result = await extractSpec({
399
- entryFile: 'test.ts',
400
- content: FIXTURE_CODE.basic,
401
- });
402
-
403
- const iface = result.spec.exports.find((e) => e.kind === 'interface');
404
- expect(iface).toBeDefined();
405
- expect(iface!.members).toBeDefined();
406
- });
407
- });
408
-
409
- describe('options', () => {
410
- test('maxTypeDepth option respected', async () => {
411
- const result = await extractSpec({
412
- entryFile: 'test.ts',
413
- content: FIXTURE_CODE.basic,
414
- maxTypeDepth: 1,
415
- });
416
-
417
- // Should complete without error with reduced depth
418
- expect(result.spec).toBeDefined();
419
- });
420
-
421
- test('resolveExternalTypes option', async () => {
422
- const result = await extractSpec({
423
- entryFile: 'test.ts',
424
- content: FIXTURE_CODE.basic,
425
- resolveExternalTypes: false,
426
- });
427
-
428
- expect(result.spec).toBeDefined();
429
- });
430
-
431
- test('schemaExtraction option', async () => {
432
- const result = await extractSpec({
433
- entryFile: 'test.ts',
434
- content: FIXTURE_CODE.basic,
435
- schemaExtraction: 'static',
436
- });
437
-
438
- expect(result.spec).toBeDefined();
439
- });
440
- });
441
-
442
- describe('performance', () => {
443
- test('spec generation completes in reasonable time', async () => {
444
- const start = performance.now();
445
- const result = await extractSpec({
446
- entryFile: 'test.ts',
447
- content: FIXTURE_CODE.basic,
448
- });
449
- const elapsed = performance.now() - start;
450
-
451
- expect(result.spec).toBeDefined();
452
- // 3s is generous for CI environments
453
- expect(elapsed).toBeLessThan(3000);
454
- });
455
-
456
- test('filtered extraction is fast', async () => {
457
- const start = performance.now();
458
- const result = await extractSpec({
459
- entryFile: 'test.ts',
460
- content: FIXTURE_CODE.forFiltering,
461
- only: ['createUser'],
462
- });
463
- const elapsed = performance.now() - start;
464
-
465
- expect(result.spec.exports.length).toBe(1);
466
- expect(elapsed).toBeLessThan(3000);
467
- });
468
- });
469
- });
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "skipLibCheck": true,
9
- "declaration": true,
10
- "outDir": "dist",
11
- "rootDir": "."
12
- },
13
- "include": ["bin/**/*", "src/**/*"],
14
- "exclude": ["node_modules", "dist"]
15
- }