@fullevent/node 0.0.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.
@@ -0,0 +1,917 @@
1
+ #!/usr/bin/env npx tsx
2
+
3
+ /**
4
+ * SDK Documentation Generator
5
+ *
6
+ * Extracts TSDoc comments from the SDK source and generates MDX files
7
+ * for the Fumadocs documentation site.
8
+ *
9
+ * Usage:
10
+ * npx tsx scripts/generate-docs.ts
11
+ * npm run generate:docs
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import * as ts from 'typescript';
17
+
18
+ // Configuration
19
+ const SDK_SRC = path.join(__dirname, '../src');
20
+ const DOCS_OUTPUT = path.join(__dirname, '../../../apps/docs/content/docs/sdk-reference');
21
+
22
+ // Types for extracted documentation
23
+ interface DocParam {
24
+ name: string;
25
+ type: string;
26
+ description: string;
27
+ optional: boolean;
28
+ defaultValue?: string;
29
+ }
30
+
31
+ interface DocExample {
32
+ title?: string;
33
+ code: string;
34
+ }
35
+
36
+ interface DocMethod {
37
+ name: string;
38
+ signature: string;
39
+ description: string;
40
+ remarks?: string;
41
+ params: DocParam[];
42
+ returns?: { type: string; description: string };
43
+ examples: DocExample[];
44
+ category?: string;
45
+ }
46
+
47
+ interface DocProperty {
48
+ name: string;
49
+ type: string;
50
+ description: string;
51
+ remarks?: string;
52
+ optional: boolean;
53
+ defaultValue?: string;
54
+ }
55
+
56
+ interface DocInterface {
57
+ name: string;
58
+ description: string;
59
+ remarks?: string;
60
+ properties: DocProperty[];
61
+ examples: DocExample[];
62
+ category?: string;
63
+ }
64
+
65
+ interface DocClass {
66
+ name: string;
67
+ description: string;
68
+ remarks?: string;
69
+ constructor?: DocMethod;
70
+ methods: DocMethod[];
71
+ properties: DocProperty[];
72
+ examples: DocExample[];
73
+ category?: string;
74
+ }
75
+
76
+ interface DocFunction {
77
+ name: string;
78
+ signature: string;
79
+ description: string;
80
+ remarks?: string;
81
+ params: DocParam[];
82
+ returns?: { type: string; description: string };
83
+ examples: DocExample[];
84
+ category?: string;
85
+ }
86
+
87
+ interface ExtractedDocs {
88
+ packageDescription: string;
89
+ classes: DocClass[];
90
+ interfaces: DocInterface[];
91
+ functions: DocFunction[];
92
+ types: DocInterface[];
93
+ }
94
+
95
+ // Helper to extract JSDoc comment
96
+ function getJSDocComment(node: ts.Node, sourceFile: ts.SourceFile): string {
97
+ const commentRanges = ts.getLeadingCommentRanges(sourceFile.text, node.pos);
98
+ if (!commentRanges) return '';
99
+
100
+ for (const range of commentRanges) {
101
+ const comment = sourceFile.text.slice(range.pos, range.end);
102
+ if (comment.startsWith('/**')) {
103
+ return comment;
104
+ }
105
+ }
106
+ return '';
107
+ }
108
+
109
+ // Parse JSDoc tags from comment
110
+ function parseJSDoc(comment: string): {
111
+ description: string;
112
+ remarks?: string;
113
+ params: Map<string, string>;
114
+ returns?: string;
115
+ examples: { title?: string; code: string }[];
116
+ defaultValue?: string;
117
+ category?: string;
118
+ see?: string[];
119
+ } {
120
+ if (!comment) {
121
+ return { description: '', params: new Map(), examples: [] };
122
+ }
123
+
124
+ // Remove /** and */ and clean up
125
+ const lines = comment
126
+ .replace(/^\/\*\*\s*/, '')
127
+ .replace(/\s*\*\/$/, '')
128
+ .split('\n')
129
+ .map(line => line.replace(/^\s*\*\s?/, ''));
130
+
131
+ let description = '';
132
+ let remarks = '';
133
+ let returns = '';
134
+ let defaultValue = '';
135
+ let category = '';
136
+ const params = new Map<string, string>();
137
+ const examples: { title?: string; code: string }[] = [];
138
+ const see: string[] = [];
139
+
140
+ let currentTag = '';
141
+ let currentContent = '';
142
+ let inExample = false;
143
+ let exampleTitle = '';
144
+ let exampleContent = '';
145
+
146
+ for (const line of lines) {
147
+ if (line.startsWith('@')) {
148
+ // Save previous tag content
149
+ if (currentTag === 'remarks') remarks = currentContent.trim();
150
+ if (currentTag === 'returns') returns = currentContent.trim();
151
+ if (currentTag === 'defaultValue') defaultValue = currentContent.trim().replace(/^`|`$/g, '');
152
+ if (currentTag === 'category') category = currentContent.trim();
153
+ if (inExample && exampleContent) {
154
+ // Extract code from code blocks
155
+ let code = exampleContent.trim();
156
+ if (code.startsWith('```')) {
157
+ code = code.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
158
+ }
159
+ examples.push({ title: exampleTitle || undefined, code: code.trim() });
160
+ exampleContent = '';
161
+ exampleTitle = '';
162
+ }
163
+ inExample = false;
164
+
165
+ const tagMatch = line.match(/^@(\w+)(?:\s+(.*))?$/);
166
+ if (tagMatch) {
167
+ currentTag = tagMatch[1];
168
+ currentContent = tagMatch[2] || '';
169
+
170
+ if (currentTag === 'param') {
171
+ const paramMatch = currentContent.match(/^(\w+)\s*-?\s*(.*)$/);
172
+ if (paramMatch) {
173
+ params.set(paramMatch[1], paramMatch[2]);
174
+ }
175
+ currentTag = '';
176
+ } else if (currentTag === 'example') {
177
+ inExample = true;
178
+ // Check if the line after @example is a title (not a code block)
179
+ exampleTitle = currentContent.trim();
180
+ exampleContent = '';
181
+ } else if (currentTag === 'see') {
182
+ see.push(currentContent);
183
+ currentTag = '';
184
+ }
185
+ }
186
+ } else if (inExample) {
187
+ exampleContent += '\n' + line;
188
+ } else if (currentTag) {
189
+ currentContent += '\n' + line;
190
+ } else if (!currentTag && line.trim()) {
191
+ description += (description ? '\n' : '') + line;
192
+ }
193
+ }
194
+
195
+ // Save final tag content
196
+ if (currentTag === 'remarks') remarks = currentContent.trim();
197
+ if (currentTag === 'returns') returns = currentContent.trim();
198
+ if (currentTag === 'defaultValue') defaultValue = currentContent.trim().replace(/^`|`$/g, '');
199
+ if (currentTag === 'category') category = currentContent.trim();
200
+ if (inExample && exampleContent) {
201
+ let code = exampleContent.trim();
202
+ if (code.startsWith('```')) {
203
+ code = code.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
204
+ }
205
+ examples.push({ title: exampleTitle || undefined, code: code.trim() });
206
+ }
207
+
208
+ return { description, remarks, params, returns, examples, defaultValue, category, see };
209
+ }
210
+
211
+ // Get type string from TypeScript node
212
+ function getTypeString(node: ts.TypeNode | undefined, sourceFile: ts.SourceFile): string {
213
+ if (!node) return 'unknown';
214
+ return node.getText(sourceFile);
215
+ }
216
+
217
+ // Extract documentation from a source file
218
+ function extractFromFile(filePath: string): ExtractedDocs {
219
+ const sourceCode = fs.readFileSync(filePath, 'utf-8');
220
+ const sourceFile = ts.createSourceFile(
221
+ path.basename(filePath),
222
+ sourceCode,
223
+ ts.ScriptTarget.Latest,
224
+ true
225
+ );
226
+
227
+ const docs: ExtractedDocs = {
228
+ packageDescription: '',
229
+ classes: [],
230
+ interfaces: [],
231
+ functions: [],
232
+ types: [],
233
+ };
234
+
235
+ // Check for @packageDocumentation
236
+ const fileComment = getJSDocComment(sourceFile, sourceFile);
237
+ if (fileComment.includes('@packageDocumentation')) {
238
+ const parsed = parseJSDoc(fileComment);
239
+ docs.packageDescription = parsed.description;
240
+ }
241
+
242
+ function visit(node: ts.Node) {
243
+ const comment = getJSDocComment(node, sourceFile);
244
+ const parsed = parseJSDoc(comment);
245
+
246
+ // Skip internal items
247
+ if (comment.includes('@internal')) {
248
+ return;
249
+ }
250
+
251
+ if (ts.isClassDeclaration(node) && node.name) {
252
+ const classDoc: DocClass = {
253
+ name: node.name.text,
254
+ description: parsed.description,
255
+ remarks: parsed.remarks,
256
+ methods: [],
257
+ properties: [],
258
+ examples: parsed.examples,
259
+ category: parsed.category,
260
+ };
261
+
262
+ for (const member of node.members) {
263
+ const memberComment = getJSDocComment(member, sourceFile);
264
+ if (memberComment.includes('@internal')) continue;
265
+
266
+ const memberParsed = parseJSDoc(memberComment);
267
+
268
+ if (ts.isConstructorDeclaration(member)) {
269
+ const params: DocParam[] = member.parameters.map(p => ({
270
+ name: p.name.getText(sourceFile),
271
+ type: getTypeString(p.type, sourceFile),
272
+ description: memberParsed.params.get(p.name.getText(sourceFile)) || '',
273
+ optional: !!p.questionToken || !!p.initializer,
274
+ defaultValue: p.initializer?.getText(sourceFile),
275
+ }));
276
+
277
+ classDoc.constructor = {
278
+ name: 'constructor',
279
+ signature: `new ${classDoc.name}(${params.map(p => `${p.name}${p.optional ? '?' : ''}: ${p.type}`).join(', ')})`,
280
+ description: memberParsed.description,
281
+ params,
282
+ examples: memberParsed.examples,
283
+ };
284
+ } else if (ts.isMethodDeclaration(member) && member.name) {
285
+ const methodName = member.name.getText(sourceFile);
286
+ if (methodName.startsWith('_')) continue; // Skip private methods
287
+
288
+ const params: DocParam[] = member.parameters.map(p => ({
289
+ name: p.name.getText(sourceFile),
290
+ type: getTypeString(p.type, sourceFile),
291
+ description: memberParsed.params.get(p.name.getText(sourceFile)) || '',
292
+ optional: !!p.questionToken || !!p.initializer,
293
+ defaultValue: p.initializer?.getText(sourceFile),
294
+ }));
295
+
296
+ const returnType = member.type ? getTypeString(member.type, sourceFile) : 'void';
297
+
298
+ classDoc.methods.push({
299
+ name: methodName,
300
+ signature: `${methodName}(${params.map(p => `${p.name}${p.optional ? '?' : ''}: ${p.type}`).join(', ')}): ${returnType}`,
301
+ description: memberParsed.description,
302
+ remarks: memberParsed.remarks,
303
+ params,
304
+ returns: memberParsed.returns ? { type: returnType, description: memberParsed.returns } : undefined,
305
+ examples: memberParsed.examples,
306
+ });
307
+ } else if (ts.isPropertyDeclaration(member) && member.name) {
308
+ const propName = member.name.getText(sourceFile);
309
+ if (propName.startsWith('_') || member.modifiers?.some(m => m.kind === ts.SyntaxKind.PrivateKeyword)) continue;
310
+
311
+ classDoc.properties.push({
312
+ name: propName,
313
+ type: getTypeString(member.type, sourceFile),
314
+ description: memberParsed.description,
315
+ remarks: memberParsed.remarks,
316
+ optional: !!member.questionToken,
317
+ defaultValue: memberParsed.defaultValue,
318
+ });
319
+ }
320
+ }
321
+
322
+ docs.classes.push(classDoc);
323
+ } else if (ts.isInterfaceDeclaration(node) && node.name) {
324
+ const interfaceDoc: DocInterface = {
325
+ name: node.name.text,
326
+ description: parsed.description,
327
+ remarks: parsed.remarks,
328
+ properties: [],
329
+ examples: parsed.examples,
330
+ category: parsed.category,
331
+ };
332
+
333
+ for (const member of node.members) {
334
+ const memberComment = getJSDocComment(member, sourceFile);
335
+ const memberParsed = parseJSDoc(memberComment);
336
+
337
+ if (ts.isPropertySignature(member) && member.name) {
338
+ interfaceDoc.properties.push({
339
+ name: member.name.getText(sourceFile),
340
+ type: getTypeString(member.type, sourceFile),
341
+ description: memberParsed.description,
342
+ remarks: memberParsed.remarks,
343
+ optional: !!member.questionToken,
344
+ defaultValue: memberParsed.defaultValue,
345
+ });
346
+ }
347
+ }
348
+
349
+ docs.interfaces.push(interfaceDoc);
350
+ } else if (ts.isFunctionDeclaration(node) && node.name) {
351
+ const params: DocParam[] = node.parameters.map(p => ({
352
+ name: p.name.getText(sourceFile),
353
+ type: getTypeString(p.type, sourceFile),
354
+ description: parsed.params.get(p.name.getText(sourceFile)) || '',
355
+ optional: !!p.questionToken || !!p.initializer,
356
+ defaultValue: p.initializer?.getText(sourceFile),
357
+ }));
358
+
359
+ const returnType = node.type ? getTypeString(node.type, sourceFile) : 'void';
360
+
361
+ docs.functions.push({
362
+ name: node.name.text,
363
+ signature: `${node.name.text}(${params.map(p => `${p.name}${p.optional ? '?' : ''}: ${p.type}`).join(', ')}): ${returnType}`,
364
+ description: parsed.description,
365
+ remarks: parsed.remarks,
366
+ params,
367
+ returns: parsed.returns ? { type: returnType, description: parsed.returns } : undefined,
368
+ examples: parsed.examples,
369
+ category: parsed.category,
370
+ });
371
+ } else if (ts.isVariableStatement(node)) {
372
+ // Handle exported const functions like `export const wideLogger = ...`
373
+ for (const decl of node.declarationList.declarations) {
374
+ if (ts.isIdentifier(decl.name) && decl.initializer && ts.isArrowFunction(decl.initializer)) {
375
+ const funcComment = getJSDocComment(node, sourceFile);
376
+ if (funcComment.includes('@internal')) continue;
377
+
378
+ const funcParsed = parseJSDoc(funcComment);
379
+ const arrow = decl.initializer;
380
+
381
+ const params: DocParam[] = arrow.parameters.map(p => ({
382
+ name: p.name.getText(sourceFile),
383
+ type: getTypeString(p.type, sourceFile),
384
+ description: funcParsed.params.get(p.name.getText(sourceFile)) || '',
385
+ optional: !!p.questionToken || !!p.initializer,
386
+ }));
387
+
388
+ const returnType = arrow.type ? getTypeString(arrow.type, sourceFile) : 'void';
389
+
390
+ docs.functions.push({
391
+ name: decl.name.text,
392
+ signature: `${decl.name.text}(${params.map(p => `${p.name}${p.optional ? '?' : ''}: ${p.type}`).join(', ')}): ${returnType}`,
393
+ description: funcParsed.description,
394
+ remarks: funcParsed.remarks,
395
+ params,
396
+ returns: funcParsed.returns ? { type: returnType, description: funcParsed.returns } : undefined,
397
+ examples: funcParsed.examples,
398
+ category: funcParsed.category,
399
+ });
400
+ }
401
+ }
402
+ } else if (ts.isTypeAliasDeclaration(node) && node.name) {
403
+ // Handle type aliases
404
+ const typeDoc: DocInterface = {
405
+ name: node.name.text,
406
+ description: parsed.description,
407
+ remarks: parsed.remarks,
408
+ properties: [],
409
+ examples: parsed.examples,
410
+ category: parsed.category,
411
+ };
412
+
413
+ if (ts.isTypeLiteralNode(node.type)) {
414
+ for (const member of node.type.members) {
415
+ const memberComment = getJSDocComment(member, sourceFile);
416
+ const memberParsed = parseJSDoc(memberComment);
417
+
418
+ if (ts.isPropertySignature(member) && member.name) {
419
+ typeDoc.properties.push({
420
+ name: member.name.getText(sourceFile),
421
+ type: getTypeString(member.type, sourceFile),
422
+ description: memberParsed.description,
423
+ remarks: memberParsed.remarks,
424
+ optional: !!member.questionToken,
425
+ defaultValue: memberParsed.defaultValue,
426
+ });
427
+ }
428
+ }
429
+ }
430
+
431
+ docs.types.push(typeDoc);
432
+ }
433
+
434
+ ts.forEachChild(node, visit);
435
+ }
436
+
437
+ visit(sourceFile);
438
+ return docs;
439
+ }
440
+
441
+ // Helper to convert {@link Name} to markdown links
442
+ function convertJSDocLinks(str: string): string {
443
+ // Map of known symbols to their doc paths
444
+ const linkMap: Record<string, string> = {
445
+ 'wideLogger': '/docs/sdk-reference/middleware/widelogger',
446
+ 'expressWideLogger': '/docs/sdk-reference/middleware/expresswidelogger',
447
+ 'WideEvent': '/docs/sdk-reference/interfaces/wideevent',
448
+ 'WideEventBuilder': '/docs/sdk-reference/classes/wideeventbuilder',
449
+ 'FullEvent': '/docs/sdk-reference/classes/fullevent',
450
+ 'FullEventConfig': '/docs/sdk-reference/interfaces/fulleventconfig',
451
+ 'HttpRequestProperties': '/docs/sdk-reference/interfaces/httprequestproperties',
452
+ 'SamplingConfig': '/docs/sdk-reference/interfaces/samplingconfig',
453
+ 'WideLoggerConfig': '/docs/sdk-reference/interfaces/wideloggerconfig',
454
+ 'WideEventVariables': '/docs/sdk-reference/types/wideeventvariables',
455
+ };
456
+
457
+ return str.replace(/\{@link\s+(\w+)\}/g, (_, name) => {
458
+ const path = linkMap[name];
459
+ if (path) {
460
+ return `[${name}](${path})`;
461
+ }
462
+ return `\`${name}\``;
463
+ });
464
+ }
465
+
466
+ // Helper to escape JSX special characters
467
+ function escapeJSX(str: string): string {
468
+ return str
469
+ .replace(/\{/g, '&#123;')
470
+ .replace(/\}/g, '&#125;')
471
+ .replace(/</g, '&lt;')
472
+ .replace(/>/g, '&gt;')
473
+ .replace(/`/g, "'"); // Replace backticks with single quotes for JSX attributes
474
+ }
475
+
476
+ // Helper to format code for JSX (preserve code blocks but escape for JSX)
477
+ function formatCodeForJSX(code: string): string {
478
+ return code.replace(/`/g, '\\`');
479
+ }
480
+
481
+ // Helper to clean default value for display (remove backticks)
482
+ function cleanDefaultValue(val: string): string {
483
+ return val.replace(/`/g, '').replace(/'/g, '');
484
+ }
485
+
486
+ // Helper to format examples for markdown
487
+ function formatExamples(examples: DocExample[]): string {
488
+ return examples.map(ex => {
489
+ let result = '';
490
+ if (ex.title) {
491
+ result += `### ${ex.title}\n\n`;
492
+ }
493
+ result += '```typescript\n' + ex.code + '\n```';
494
+ return result;
495
+ }).join('\n\n');
496
+ }
497
+
498
+ // Generate MDX content for a class (interactive accordion style)
499
+ function generateClassMDX(cls: DocClass): string {
500
+ const firstExample = cls.examples[0]?.code || '';
501
+ const description = convertJSDocLinks(cls.description);
502
+
503
+ let mdx = `---
504
+ title: ${cls.name}
505
+ description: ${description.split('\n')[0]}
506
+ ---
507
+
508
+ # ${cls.name}
509
+
510
+ ${description}
511
+
512
+ `;
513
+
514
+ if (cls.remarks) {
515
+ mdx += `${convertJSDocLinks(cls.remarks)}
516
+
517
+ `;
518
+ }
519
+
520
+ if (cls.examples.length > 0) {
521
+ mdx += `## Usage
522
+
523
+ ${formatExamples(cls.examples)}
524
+
525
+ `;
526
+ }
527
+
528
+ // Methods as accordion
529
+ if (cls.methods.length > 0 || cls.constructor) {
530
+ mdx += `## API Reference
531
+
532
+ <APIReference>
533
+ `;
534
+
535
+ // Constructor
536
+ if (cls.constructor) {
537
+ const ctorParams = cls.constructor.params;
538
+ const ctorExample = cls.constructor.examples[0]?.code || `const instance = new ${cls.name}(${ctorParams.map(p => p.name).join(', ')});`;
539
+
540
+ mdx += `
541
+ <APIMethod name="new ${cls.name}(${ctorParams.map(p => p.name).join(', ')})" category="${cls.name}">
542
+ <APIMethodContent>
543
+ <APIMethodLeft>
544
+ <APIDescription>
545
+ ${cls.constructor.description}
546
+ </APIDescription>
547
+ ${ctorParams.length > 0 ? `
548
+ <APIParameters>
549
+ ${ctorParams.map(p => `<APIParam name="${p.name}" type="${escapeJSX(p.type)}" ${p.optional ? '' : 'required'}${p.defaultValue ? ` defaultValue="${p.defaultValue}"` : ''}>
550
+ ${p.description}
551
+ </APIParam>`).join('\n')}
552
+ </APIParameters>
553
+ ` : ''}
554
+ </APIMethodLeft>
555
+ <APIMethodRight>
556
+ <APISignature>{\`${formatCodeForJSX(cls.constructor.signature)}\`}</APISignature>
557
+ <APIExamples>{\`${formatCodeForJSX(ctorExample)}\`}</APIExamples>
558
+ </APIMethodRight>
559
+ </APIMethodContent>
560
+ </APIMethod>
561
+ `;
562
+ }
563
+
564
+ // Methods
565
+ for (const method of cls.methods) {
566
+ const methodParams = method.params;
567
+ const methodExample = method.examples[0]?.code || `instance.${method.name}(${methodParams.map(p => p.name).join(', ')});`;
568
+ const note = method.remarks ? method.remarks.split('\n')[0] : '';
569
+
570
+ mdx += `
571
+ <APIMethod name="${cls.name.toLowerCase()}.${method.name}(${methodParams.map(p => p.name).join(', ')})" category="${cls.name}">
572
+ <APIMethodContent>
573
+ <APIMethodLeft>
574
+ <APIDescription${note ? ` note="${escapeJSX(note)}"` : ''}>
575
+ ${method.description}
576
+ </APIDescription>
577
+ ${methodParams.length > 0 ? `
578
+ <APIParameters>
579
+ ${methodParams.map(p => `<APIParam name="${p.name}" type="${escapeJSX(p.type)}" ${p.optional ? '' : 'required'}${p.defaultValue ? ` defaultValue="${p.defaultValue}"` : ''}>
580
+ ${p.description}
581
+ </APIParam>`).join('\n')}
582
+ </APIParameters>
583
+ ` : ''}
584
+ ${method.returns ? `<APIReturns type="${escapeJSX(method.returns.type)}">${method.returns.description}</APIReturns>` : ''}
585
+ </APIMethodLeft>
586
+ <APIMethodRight>
587
+ <APISignature>{\`${formatCodeForJSX(method.signature)}\`}</APISignature>
588
+ <APIExamples>{\`${formatCodeForJSX(methodExample)}\`}</APIExamples>
589
+ </APIMethodRight>
590
+ </APIMethodContent>
591
+ </APIMethod>
592
+ `;
593
+ }
594
+
595
+ mdx += `
596
+ </APIReference>
597
+ `;
598
+ }
599
+
600
+ return mdx;
601
+ }
602
+
603
+ // Generate MDX content for an interface (interactive style)
604
+ function generateInterfaceMDX(iface: DocInterface): string {
605
+ const description = convertJSDocLinks(iface.description);
606
+
607
+ let mdx = `---
608
+ title: ${iface.name}
609
+ description: ${description.split('\n')[0] || 'Interface documentation'}
610
+ ---
611
+
612
+ # ${iface.name}
613
+
614
+ ${description}
615
+
616
+ `;
617
+
618
+ if (iface.remarks) {
619
+ mdx += `${convertJSDocLinks(iface.remarks)}
620
+
621
+ `;
622
+ }
623
+
624
+ if (iface.examples.length > 0) {
625
+ mdx += `## Usage
626
+
627
+ ${formatExamples(iface.examples)}
628
+
629
+ `;
630
+ }
631
+
632
+ if (iface.properties.length > 0) {
633
+ mdx += `## Properties
634
+
635
+ <APIReference>
636
+ `;
637
+
638
+ for (const prop of iface.properties) {
639
+ const propDesc = prop.description || '';
640
+ const cleanedDefault = prop.defaultValue ? cleanDefaultValue(prop.defaultValue) : '';
641
+
642
+ mdx += `
643
+ <APIMethod name="${iface.name}.${prop.name}" category="${iface.name}">
644
+ <APIMethodContent>
645
+ <APIMethodLeft>
646
+ <APIDescription>
647
+ ${propDesc}
648
+ ${prop.remarks ? `\n${prop.remarks}` : ''}
649
+ </APIDescription>
650
+ <APIParameters>
651
+ <APIParam name="${prop.name}" type="${escapeJSX(prop.type)}" ${prop.optional ? '' : 'required'}${cleanedDefault ? ` defaultValue="${escapeJSX(cleanedDefault)}"` : ''}>
652
+ ${propDesc}
653
+ </APIParam>
654
+ </APIParameters>
655
+ </APIMethodLeft>
656
+ <APIMethodRight>
657
+ <APISignature>{\`${prop.name}${prop.optional ? '?' : ''}: ${escapeJSX(prop.type)}\`}</APISignature>
658
+ ${cleanedDefault ? `<APIExamples>{\`// Default: ${formatCodeForJSX(cleanedDefault)}\`}</APIExamples>` : ''}
659
+ </APIMethodRight>
660
+ </APIMethodContent>
661
+ </APIMethod>
662
+ `;
663
+ }
664
+
665
+ mdx += `
666
+ </APIReference>
667
+ `;
668
+ }
669
+
670
+ return mdx;
671
+ }
672
+
673
+ // Generate MDX content for a function (interactive style)
674
+ function generateFunctionMDX(func: DocFunction): string {
675
+ const funcExample = func.examples[0]?.code || `${func.name}(${func.params.map(p => p.name).join(', ')});`;
676
+ const description = convertJSDocLinks(func.description);
677
+ const remarks = func.remarks ? convertJSDocLinks(func.remarks) : '';
678
+ const note = remarks ? remarks.split('\n')[0] : '';
679
+
680
+ let mdx = `---
681
+ title: ${func.name}
682
+ description: ${description.split('\n')[0]}
683
+ ---
684
+
685
+ # ${func.name}
686
+
687
+ ${description}
688
+
689
+ `;
690
+
691
+ if (remarks) {
692
+ mdx += `${remarks}
693
+
694
+ `;
695
+ }
696
+
697
+ mdx += `## API Reference
698
+
699
+ <APIReference>
700
+ <APIMethod name="${func.name}(${func.params.map(p => p.name).join(', ')})" category="Function">
701
+ <APIMethodContent>
702
+ <APIMethodLeft>
703
+ <APIDescription${note ? ` note="${escapeJSX(note)}"` : ''}>
704
+ ${description}
705
+ </APIDescription>
706
+ ${func.params.length > 0 ? `
707
+ <APIParameters>
708
+ ${func.params.map(p => `<APIParam name="${p.name}" type="${escapeJSX(p.type)}" ${p.optional ? '' : 'required'}${p.defaultValue ? ` defaultValue="${p.defaultValue}"` : ''}>
709
+ ${p.description}
710
+ </APIParam>`).join('\n')}
711
+ </APIParameters>
712
+ ` : ''}
713
+ ${func.returns ? `<APIReturns type="${escapeJSX(func.returns.type)}">${func.returns.description}</APIReturns>` : ''}
714
+ </APIMethodLeft>
715
+ <APIMethodRight>
716
+ <APISignature>{\`${formatCodeForJSX(func.signature)}\`}</APISignature>
717
+ <APIExamples>{\`${formatCodeForJSX(funcExample)}\`}</APIExamples>
718
+ </APIMethodRight>
719
+ </APIMethodContent>
720
+ </APIMethod>
721
+ </APIReference>
722
+ `;
723
+
724
+ // Additional examples if there are more
725
+ if (func.examples.length > 1) {
726
+ mdx += `
727
+ ## More Examples
728
+
729
+ ${formatExamples(func.examples.slice(1))}
730
+ `;
731
+ }
732
+
733
+ return mdx;
734
+ }
735
+
736
+ // Main function
737
+ async function main() {
738
+ console.log('📚 Generating SDK documentation...\n');
739
+
740
+ // Ensure output directory exists
741
+ if (!fs.existsSync(DOCS_OUTPUT)) {
742
+ fs.mkdirSync(DOCS_OUTPUT, { recursive: true });
743
+ }
744
+
745
+ // Source files to process
746
+ const sourceFiles = [
747
+ 'index.ts',
748
+ 'client.ts',
749
+ 'builder.ts',
750
+ 'middleware/hono.ts',
751
+ 'middleware/express.ts',
752
+ ];
753
+
754
+ const allDocs: ExtractedDocs = {
755
+ packageDescription: '',
756
+ classes: [],
757
+ interfaces: [],
758
+ functions: [],
759
+ types: [],
760
+ };
761
+
762
+ // Extract docs from each file
763
+ for (const file of sourceFiles) {
764
+ const filePath = path.join(SDK_SRC, file);
765
+ if (fs.existsSync(filePath)) {
766
+ console.log(` Processing ${file}...`);
767
+ const docs = extractFromFile(filePath);
768
+
769
+ if (docs.packageDescription) allDocs.packageDescription = docs.packageDescription;
770
+ allDocs.classes.push(...docs.classes);
771
+ allDocs.interfaces.push(...docs.interfaces);
772
+ allDocs.functions.push(...docs.functions);
773
+ allDocs.types.push(...docs.types);
774
+ }
775
+ }
776
+
777
+ console.log(`\n Found:`);
778
+ console.log(` - ${allDocs.classes.length} classes`);
779
+ console.log(` - ${allDocs.interfaces.length} interfaces`);
780
+ console.log(` - ${allDocs.functions.length} functions`);
781
+ console.log(` - ${allDocs.types.length} types`);
782
+
783
+ // Filter interfaces for index page too (skip Express's Request extension)
784
+ const validInterfaces = allDocs.interfaces.filter(i =>
785
+ (i.description || i.properties.length > 0 || i.examples.length > 0) &&
786
+ i.name !== 'Request'
787
+ );
788
+
789
+ // Generate index page
790
+ const indexMDX = `---
791
+ title: SDK Reference
792
+ description: Complete API reference for the FullEvent Node.js SDK
793
+ ---
794
+
795
+ # SDK Reference
796
+
797
+ ${allDocs.packageDescription || 'Complete API reference for the FullEvent Node.js SDK.'}
798
+
799
+ ## Classes
800
+
801
+ ${allDocs.classes.map(c => `- [${c.name}](/docs/sdk-reference/classes/${c.name.toLowerCase()}) - ${c.description.split('\n')[0]}`).join('\n')}
802
+
803
+ ## Middleware
804
+
805
+ ${allDocs.functions.filter(f => f.category === 'Middleware').map(f => `- [${f.name}](/docs/sdk-reference/middleware/${f.name.toLowerCase()}) - ${f.description.split('\n')[0]}`).join('\n')}
806
+
807
+ ## Interfaces
808
+
809
+ ${validInterfaces.map(i => `- [${i.name}](/docs/sdk-reference/interfaces/${i.name.toLowerCase()}) - ${i.description.split('\n')[0]}`).join('\n')}
810
+
811
+ ## Types
812
+
813
+ ${allDocs.types.map(t => `- [${t.name}](/docs/sdk-reference/types/${t.name.toLowerCase()}) - ${t.description.split('\n')[0]}`).join('\n')}
814
+ `;
815
+
816
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'index.mdx'), indexMDX);
817
+ console.log(`\n Generated index.mdx`);
818
+
819
+ // Create subdirectories
820
+ const subdirs = ['classes', 'interfaces', 'middleware', 'types'];
821
+ for (const subdir of subdirs) {
822
+ const subdirPath = path.join(DOCS_OUTPUT, subdir);
823
+ if (!fs.existsSync(subdirPath)) {
824
+ fs.mkdirSync(subdirPath, { recursive: true });
825
+ }
826
+ }
827
+
828
+ // Generate class pages
829
+ for (const cls of allDocs.classes) {
830
+ const mdx = generateClassMDX(cls);
831
+ const filename = `${cls.name.toLowerCase()}.mdx`;
832
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'classes', filename), mdx);
833
+ console.log(` Generated classes/${filename}`);
834
+ }
835
+
836
+ // Generate interface pages (skip empty/internal interfaces like Express's Request declaration)
837
+ const filteredInterfaces = allDocs.interfaces.filter(i =>
838
+ (i.description || i.properties.length > 0 || i.examples.length > 0) &&
839
+ i.name !== 'Request' // Skip Express's Request extension
840
+ );
841
+ for (const iface of filteredInterfaces) {
842
+ const mdx = generateInterfaceMDX(iface);
843
+ const filename = `${iface.name.toLowerCase()}.mdx`;
844
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'interfaces', filename), mdx);
845
+ console.log(` Generated interfaces/${filename}`);
846
+ }
847
+
848
+ // Generate function/middleware pages
849
+ for (const func of allDocs.functions) {
850
+ const mdx = generateFunctionMDX(func);
851
+ const filename = `${func.name.toLowerCase()}.mdx`;
852
+ const subdir = func.category === 'Middleware' ? 'middleware' : 'functions';
853
+ const subdirPath = path.join(DOCS_OUTPUT, subdir);
854
+ if (!fs.existsSync(subdirPath)) {
855
+ fs.mkdirSync(subdirPath, { recursive: true });
856
+ }
857
+ fs.writeFileSync(path.join(subdirPath, filename), mdx);
858
+ console.log(` Generated ${subdir}/${filename}`);
859
+ }
860
+
861
+ // Generate type pages
862
+ for (const type of allDocs.types) {
863
+ const mdx = generateInterfaceMDX(type);
864
+ const filename = `${type.name.toLowerCase()}.mdx`;
865
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'types', filename), mdx);
866
+ console.log(` Generated types/${filename}`);
867
+ }
868
+
869
+ // Generate meta.json for Fumadocs navigation
870
+ const middlewareFuncs = allDocs.functions.filter(f => f.category === 'Middleware');
871
+
872
+ const meta = {
873
+ title: 'SDK Reference',
874
+ pages: [
875
+ '...', // Auto-sort remaining pages
876
+ ],
877
+ // Define the order with separators
878
+ defaultOpen: true,
879
+ };
880
+
881
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'meta.json'), JSON.stringify(meta, null, 2));
882
+ console.log(` Generated meta.json`);
883
+
884
+ // Generate meta.json for classes subdirectory
885
+ const classesMeta = {
886
+ title: 'Classes',
887
+ pages: allDocs.classes.map(c => c.name.toLowerCase()),
888
+ };
889
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'classes', 'meta.json'), JSON.stringify(classesMeta, null, 2));
890
+
891
+ // Generate meta.json for interfaces subdirectory
892
+ const interfacesMeta = {
893
+ title: 'Interfaces',
894
+ pages: validInterfaces.map(i => i.name.toLowerCase()),
895
+ };
896
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'interfaces', 'meta.json'), JSON.stringify(interfacesMeta, null, 2));
897
+
898
+ // Generate meta.json for middleware subdirectory
899
+ const middlewareMeta = {
900
+ title: 'Middleware',
901
+ pages: middlewareFuncs.map(f => f.name.toLowerCase()),
902
+ };
903
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'middleware', 'meta.json'), JSON.stringify(middlewareMeta, null, 2));
904
+
905
+ // Generate meta.json for types subdirectory
906
+ const typesMeta = {
907
+ title: 'Types',
908
+ pages: allDocs.types.map(t => t.name.toLowerCase()),
909
+ };
910
+ fs.writeFileSync(path.join(DOCS_OUTPUT, 'types', 'meta.json'), JSON.stringify(typesMeta, null, 2));
911
+
912
+ console.log('\n✅ Documentation generated successfully!');
913
+ console.log(` Output: ${DOCS_OUTPUT}`);
914
+ }
915
+
916
+ main().catch(console.error);
917
+