skrypt-ai 0.3.4 → 0.4.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.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.d.ts +0 -1
  3. package/dist/auth/index.js +3 -5
  4. package/dist/autofix/index.js +15 -3
  5. package/dist/cli.js +19 -4
  6. package/dist/commands/check-links.js +164 -174
  7. package/dist/commands/deploy.js +5 -2
  8. package/dist/commands/generate.js +206 -199
  9. package/dist/commands/i18n.js +3 -20
  10. package/dist/commands/init.js +47 -40
  11. package/dist/commands/lint.js +3 -20
  12. package/dist/commands/mcp.js +125 -122
  13. package/dist/commands/monitor.js +125 -108
  14. package/dist/commands/review-pr.js +1 -1
  15. package/dist/commands/sdk.js +1 -1
  16. package/dist/config/loader.js +21 -2
  17. package/dist/generator/organizer.d.ts +3 -0
  18. package/dist/generator/organizer.js +4 -9
  19. package/dist/generator/writer.js +2 -10
  20. package/dist/github/pr-comments.js +21 -8
  21. package/dist/plugins/index.js +1 -0
  22. package/dist/scanner/index.js +8 -2
  23. package/dist/template/docs.json +2 -1
  24. package/dist/template/next.config.mjs +2 -1
  25. package/dist/template/package.json +17 -15
  26. package/dist/template/public/favicon.svg +4 -0
  27. package/dist/template/public/search-index.json +1 -1
  28. package/dist/template/scripts/build-search-index.mjs +120 -25
  29. package/dist/template/src/app/api/chat/route.ts +11 -3
  30. package/dist/template/src/app/docs/README.md +28 -0
  31. package/dist/template/src/app/docs/[...slug]/page.tsx +139 -16
  32. package/dist/template/src/app/docs/auth/page.mdx +589 -0
  33. package/dist/template/src/app/docs/autofix/page.mdx +624 -0
  34. package/dist/template/src/app/docs/cli/page.mdx +217 -0
  35. package/dist/template/src/app/docs/config/page.mdx +428 -0
  36. package/dist/template/src/app/docs/configuration/page.mdx +86 -0
  37. package/dist/template/src/app/docs/deployment/page.mdx +112 -0
  38. package/dist/template/src/app/docs/error.tsx +20 -0
  39. package/dist/template/src/app/docs/generator/generator.md +504 -0
  40. package/dist/template/src/app/docs/generator/organizer.md +779 -0
  41. package/dist/template/src/app/docs/generator/page.mdx +613 -0
  42. package/dist/template/src/app/docs/github/page.mdx +502 -0
  43. package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
  44. package/dist/template/src/app/docs/llm/index.md +471 -0
  45. package/dist/template/src/app/docs/llm/page.mdx +428 -0
  46. package/dist/template/src/app/docs/llms-full.md +256 -0
  47. package/dist/template/src/app/docs/llms.txt +2971 -0
  48. package/dist/template/src/app/docs/not-found.tsx +23 -0
  49. package/dist/template/src/app/docs/page.mdx +0 -3
  50. package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
  51. package/dist/template/src/app/docs/pro/page.mdx +121 -0
  52. package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
  53. package/dist/template/src/app/docs/scanner/content-type.md +599 -0
  54. package/dist/template/src/app/docs/scanner/index.md +212 -0
  55. package/dist/template/src/app/docs/scanner/page.mdx +307 -0
  56. package/dist/template/src/app/docs/scanner/python.md +469 -0
  57. package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
  58. package/dist/template/src/app/docs/scanner/rust.md +325 -0
  59. package/dist/template/src/app/docs/scanner/typescript.md +201 -0
  60. package/dist/template/src/app/error.tsx +3 -3
  61. package/dist/template/src/app/icon.tsx +29 -0
  62. package/dist/template/src/app/layout.tsx +42 -0
  63. package/dist/template/src/app/not-found.tsx +35 -0
  64. package/dist/template/src/app/page.tsx +62 -28
  65. package/dist/template/src/components/ai-chat.tsx +26 -21
  66. package/dist/template/src/components/breadcrumbs.tsx +46 -2
  67. package/dist/template/src/components/copy-button.tsx +17 -3
  68. package/dist/template/src/components/docs-layout.tsx +142 -8
  69. package/dist/template/src/components/feedback.tsx +4 -2
  70. package/dist/template/src/components/footer.tsx +42 -0
  71. package/dist/template/src/components/header.tsx +29 -5
  72. package/dist/template/src/components/mdx/accordion.tsx +7 -6
  73. package/dist/template/src/components/mdx/card.tsx +19 -7
  74. package/dist/template/src/components/mdx/code-block.tsx +17 -3
  75. package/dist/template/src/components/mdx/code-group.tsx +65 -18
  76. package/dist/template/src/components/mdx/code-playground.tsx +3 -0
  77. package/dist/template/src/components/mdx/go-playground.tsx +3 -0
  78. package/dist/template/src/components/mdx/highlighted-code.tsx +171 -76
  79. package/dist/template/src/components/mdx/python-playground.tsx +2 -0
  80. package/dist/template/src/components/mdx/tabs.tsx +74 -6
  81. package/dist/template/src/components/page-header.tsx +19 -0
  82. package/dist/template/src/components/scroll-to-top.tsx +33 -0
  83. package/dist/template/src/components/search-dialog.tsx +206 -52
  84. package/dist/template/src/components/sidebar.tsx +136 -77
  85. package/dist/template/src/components/table-of-contents.tsx +23 -7
  86. package/dist/template/src/lib/highlight.ts +90 -31
  87. package/dist/template/src/lib/search.ts +14 -4
  88. package/dist/template/src/lib/theme-utils.ts +140 -0
  89. package/dist/template/src/styles/globals.css +307 -166
  90. package/dist/template/src/types/remark-gfm.d.ts +2 -0
  91. package/dist/utils/files.d.ts +9 -0
  92. package/dist/utils/files.js +33 -0
  93. package/dist/utils/validation.d.ts +4 -0
  94. package/dist/utils/validation.js +38 -0
  95. package/package.json +1 -4
@@ -0,0 +1,779 @@
1
+ # Organizer.ts
2
+
3
+ ## Functions
4
+
5
+ ### `organizeByTopic`
6
+
7
+ ```typescript
8
+ function organizeByTopic(docs: GeneratedDoc[], config: TopicConfig = DEFAULT_TOPIC_CONFIG): Topic[]
9
+ ```
10
+
11
+ Use this to group a flat list of generated documentation objects into organized topic clusters — ideal for building navigation menus, documentation sidebars, or categorized API references.
12
+
13
+ ## Parameters
14
+
15
+ | Name | Type | Required | Description |
16
+ |------|------|----------|-------------|
17
+ | `docs` | `GeneratedDoc[]` | Yes | Array of generated documentation objects to organize |
18
+ | `config` | `TopicConfig` | No | Configuration controlling how topics are detected and grouped. Defaults to `DEFAULT_TOPIC_CONFIG` |
19
+
20
+ ## Returns
21
+
22
+ Returns a `Topic[]` array where each `Topic` contains a label, slug, and the subset of `GeneratedDoc` items belonging to that topic. Returns an empty array if `docs` is empty.
23
+
24
+ **Example:**
25
+
26
+ ```typescript example.ts
27
+ // ── Inline types (no external imports needed) ──────────────────────────────
28
+
29
+ type GeneratedDoc = {
30
+ id: string
31
+ title: string
32
+ topic: string
33
+ content: string
34
+ slug: string
35
+ }
36
+
37
+ type Topic = {
38
+ label: string
39
+ slug: string
40
+ docs: GeneratedDoc[]
41
+ }
42
+
43
+ type TopicConfig = {
44
+ defaultTopic: string
45
+ topicOrder?: string[]
46
+ normalize?: (topic: string) => string
47
+ }
48
+
49
+ // ── Inline DEFAULT_TOPIC_CONFIG ────────────────────────────────────────────
50
+
51
+ const DEFAULT_TOPIC_CONFIG: TopicConfig = {
52
+ defaultTopic: "General",
53
+ topicOrder: [],
54
+ normalize: (topic: string) => topic.trim().toLowerCase().replace(/\s+/g, "-"),
55
+ }
56
+
57
+ // ── Inline organizeByTopic implementation ─────────────────────────────────
58
+
59
+ function organizeByTopic(
60
+ docs: GeneratedDoc[],
61
+ config: TopicConfig = DEFAULT_TOPIC_CONFIG
62
+ ): Topic[] {
63
+ const topicDocs = new Map<string, GeneratedDoc[]>()
64
+
65
+ for (const doc of docs) {
66
+ const rawTopic = doc.topic || config.defaultTopic
67
+ const label = rawTopic.trim()
68
+ const existing = topicDocs.get(label) ?? []
69
+ topicDocs.set(label, [...existing, doc])
70
+ }
71
+
72
+ const topics: Topic[] = []
73
+
74
+ // Respect topicOrder if provided
75
+ const ordered = config.topicOrder ?? []
76
+ const allLabels = [...new Set([...ordered, ...topicDocs.keys()])]
77
+
78
+ for (const label of allLabels) {
79
+ const docsForTopic = topicDocs.get(label)
80
+ if (!docsForTopic) continue
81
+
82
+ const slug = config.normalize
83
+ ? config.normalize(label)
84
+ : label.toLowerCase().replace(/\s+/g, "-")
85
+
86
+ topics.push({ label, slug, docs: docsForTopic })
87
+ }
88
+
89
+ return topics
90
+ }
91
+
92
+ // ── Realistic usage example ────────────────────────────────────────────────
93
+
94
+ const generatedDocs: GeneratedDoc[] = [
95
+ {
96
+ id: "doc-001",
97
+ title: "createUser",
98
+ topic: "Authentication",
99
+ content: "Creates a new user account with the given credentials.",
100
+ slug: "create-user",
101
+ },
102
+ {
103
+ id: "doc-002",
104
+ title: "verifyToken",
105
+ topic: "Authentication",
106
+ content: "Validates a JWT and returns the decoded payload.",
107
+ slug: "verify-token",
108
+ },
109
+ {
110
+ id: "doc-003",
111
+ title: "uploadFile",
112
+ topic: "Storage",
113
+ content: "Uploads a file to the configured storage bucket.",
114
+ slug: "upload-file",
115
+ },
116
+ {
117
+ id: "doc-004",
118
+ title: "deleteFile",
119
+ topic: "Storage",
120
+ content: "Removes a file from storage by its key.",
121
+ slug: "delete-file",
122
+ },
123
+ {
124
+ id: "doc-005",
125
+ title: "sendEmail",
126
+ topic: "Notifications",
127
+ content: "Sends a transactional email via the configured provider.",
128
+ slug: "send-email",
129
+ },
130
+ {
131
+ id: "doc-006",
132
+ title: "getRateLimits",
133
+ topic: "", // falls back to defaultTopic
134
+ content: "Returns current rate limit status for the API key.",
135
+ slug: "get-rate-limits",
136
+ },
137
+ ]
138
+
139
+ const customConfig: TopicConfig = {
140
+ defaultTopic: "General",
141
+ topicOrder: ["Authentication", "Storage", "Notifications"],
142
+ normalize: (topic) => topic.toLowerCase().replace(/\s+/g, "-"),
143
+ }
144
+
145
+ async function main() {
146
+ try {
147
+ const topics = organizeByTopic(generatedDocs, customConfig)
148
+
149
+ console.log(`Organized into ${topics.length} topics:\n`)
150
+
151
+ for (const topic of topics) {
152
+ console.log(`📂 ${topic.label} (slug: "${topic.slug}")`)
153
+ for (const doc of topic.docs) {
154
+ console.log(` • ${doc.title} → /${topic.slug}/${doc.slug}`)
155
+ }
156
+ }
157
+
158
+ // Expected output:
159
+ // Organized into 4 topics:
160
+ //
161
+ // 📂 Authentication (slug: "authentication")
162
+ // • createUser → /authentication/create-user
163
+ // • verifyToken → /authentication/verify-token
164
+ // 📂 Storage (slug: "storage")
165
+ // • uploadFile → /storage/upload-file
166
+ // • deleteFile → /storage/delete-file
167
+ // 📂 Notifications (slug: "notifications")
168
+ // • sendEmail → /notifications/send-email
169
+ // 📂 General (slug: "general")
170
+ // • getRateLimits → /general/get-rate-limits
171
+ } catch (error) {
172
+ console.error("Failed to organize docs:", error)
173
+ }
174
+ }
175
+
176
+ main()
177
+ ```
178
+
179
+ ### `detectCrossReferences`
180
+
181
+ ```typescript
182
+ function detectCrossReferences(docs: GeneratedDoc[]): CrossReference[]
183
+ ```
184
+
185
+ Use this to automatically discover relationships between documented elements — for example, when one function references another by name in its parameters, return type, or description.
186
+
187
+ Given an array of generated documentation objects, this scans each element and identifies where one doc references another by name, returning a flat list of directed cross-references you can use to build navigation links, dependency graphs, or "See Also" sections.
188
+
189
+ ## Parameters
190
+
191
+ | Name | Type | Required | Description |
192
+ |------|------|----------|-------------|
193
+ | `docs` | `GeneratedDoc[]` | ✅ | Array of generated documentation objects. Each must have an `element` with a `name` property and associated metadata to scan for references. |
194
+
195
+ ## Returns
196
+
197
+ Returns `CrossReference[]` — an array of cross-reference objects. Each entry describes a directed link from a **source** element to a **target** element found within it.
198
+
199
+ | Field | Type | Description |
200
+ |-------|------|-------------|
201
+ | `from` | `string` | Name of the element that contains the reference |
202
+ | `to` | `string` | Name of the element being referenced |
203
+ | `type` | `string` | The kind of relationship (e.g. `"parameter"`, `"return"`, `"mention"`) |
204
+
205
+ Returns an **empty array** if no cross-references are detected or if fewer than two docs are provided.
206
+
207
+ **Example:**
208
+
209
+ ```typescript example.ts
210
+ // ─── Inline types (do NOT import from autodocs) ───────────────────────────────
211
+
212
+ type DocElement = {
213
+ name: string;
214
+ params?: { type: string }[];
215
+ returnType?: string;
216
+ description?: string;
217
+ };
218
+
219
+ type GeneratedDoc = {
220
+ element: DocElement;
221
+ topics: string[];
222
+ };
223
+
224
+ type CrossReference = {
225
+ from: string;
226
+ to: string;
227
+ type: "parameter" | "return" | "mention";
228
+ };
229
+
230
+ // ─── Simulated implementation of detectCrossReferences ───────────────────────
231
+
232
+ function detectCrossReferences(docs: GeneratedDoc[]): CrossReference[] {
233
+ const refs: CrossReference[] = [];
234
+ const elementNames = new Set(docs.map((d) => d.element.name));
235
+
236
+ for (const doc of docs) {
237
+ const { element } = doc;
238
+
239
+ // Check parameter types for references to other documented elements
240
+ for (const param of element.params ?? []) {
241
+ if (elementNames.has(param.type) && param.type !== element.name) {
242
+ refs.push({ from: element.name, to: param.type, type: "parameter" });
243
+ }
244
+ }
245
+
246
+ // Check return type
247
+ if (
248
+ element.returnType &&
249
+ elementNames.has(element.returnType) &&
250
+ element.returnType !== element.name
251
+ ) {
252
+ refs.push({ from: element.name, to: element.returnType, type: "return" });
253
+ }
254
+
255
+ // Check description for mentions of other element names
256
+ if (element.description) {
257
+ for (const name of elementNames) {
258
+ if (name !== element.name && element.description.includes(name)) {
259
+ refs.push({ from: element.name, to: name, type: "mention" });
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ return refs;
266
+ }
267
+
268
+ // ─── Realistic example data ───────────────────────────────────────────────────
269
+
270
+ const docs: GeneratedDoc[] = [
271
+ {
272
+ element: {
273
+ name: "UserProfile",
274
+ params: [],
275
+ returnType: undefined,
276
+ description: "Represents a user account in the system.",
277
+ },
278
+ topics: ["models"],
279
+ },
280
+ {
281
+ element: {
282
+ name: "fetchUserProfile",
283
+ params: [{ type: "string" }],
284
+ returnType: "UserProfile",
285
+ description: "Fetches a UserProfile from the remote API by user ID.",
286
+ },
287
+ topics: ["api", "users"],
288
+ },
289
+ {
290
+ element: {
291
+ name: "updateUserProfile",
292
+ params: [{ type: "UserProfile" }],
293
+ returnType: "UserProfile",
294
+ description: "Updates and returns the modified UserProfile record.",
295
+ },
296
+ topics: ["api", "users"],
297
+ },
298
+ {
299
+ element: {
300
+ name: "deleteUser",
301
+ params: [{ type: "string" }],
302
+ returnType: "boolean",
303
+ description: "Permanently removes a user. See also fetchUserProfile.",
304
+ },
305
+ topics: ["api", "users"],
306
+ },
307
+ ];
308
+
309
+ // ─── Run and display results ──────────────────────────────────────────────────
310
+
311
+ async function main() {
312
+ try {
313
+ const crossRefs = detectCrossReferences(docs);
314
+
315
+ if (crossRefs.length === 0) {
316
+ console.log("No cross-references detected.");
317
+ return;
318
+ }
319
+
320
+ console.log(`Detected ${crossRefs.length} cross-reference(s):\n`);
321
+
322
+ for (const ref of crossRefs) {
323
+ console.log(` [${ref.type.toUpperCase()}] ${ref.from} → ${ref.to}`);
324
+ }
325
+
326
+ // Expected output:
327
+ // Detected 5 cross-reference(s):
328
+ //
329
+ // [RETURN] fetchUserProfile → UserProfile
330
+ // [PARAMETER] updateUserProfile → UserProfile
331
+ // [RETURN] updateUserProfile → UserProfile
332
+ // [MENTION] deleteUser → fetchUserProfile
333
+ } catch (error) {
334
+ console.error("detectCrossReferences failed:", error);
335
+ }
336
+ }
337
+
338
+ main();
339
+ ```
340
+
341
+ ### `getCrossRefsForElement`
342
+
343
+ ```typescript
344
+ function getCrossRefsForElement(elementName: string, allRefs: CrossReference[]): CrossReference[]
345
+ ```
346
+
347
+ Use this to filter a list of cross-references down to only those originating from a specific element — useful when building navigation, dependency graphs, or "see also" sections for a given function, class, or module.
348
+
349
+ Given an element name and a full list of cross-references, it returns only the references where `fromElement` matches the provided name.
350
+
351
+ ### Parameters
352
+
353
+ | Name | Type | Required | Description |
354
+ |------|------|----------|-------------|
355
+ | `elementName` | `string` | ✅ | The name of the element whose outgoing cross-references you want to retrieve |
356
+ | `allRefs` | `CrossReference[]` | ✅ | The complete list of cross-references to filter from |
357
+
358
+ ### Returns
359
+
360
+ `CrossReference[]` — A filtered array containing only the cross-references where `fromElement === elementName`. Returns an empty array if no matches are found.
361
+
362
+ **Example:**
363
+
364
+ ```typescript example.ts
365
+ // Inline the CrossReference type (matches the real shape from autodocs)
366
+ type CrossReference = {
367
+ fromElement: string
368
+ toElement: string
369
+ description?: string
370
+ }
371
+
372
+ // Inline the function implementation
373
+ function getCrossRefsForElement(
374
+ elementName: string,
375
+ allRefs: CrossReference[]
376
+ ): CrossReference[] {
377
+ return allRefs.filter(r => r.fromElement === elementName)
378
+ }
379
+
380
+ // Realistic cross-reference data across several elements
381
+ const allCrossRefs: CrossReference[] = [
382
+ { fromElement: 'UserService', toElement: 'AuthService', description: 'Delegates login to AuthService' },
383
+ { fromElement: 'UserService', toElement: 'UserRepository', description: 'Reads/writes user records' },
384
+ { fromElement: 'AuthService', toElement: 'TokenService', description: 'Issues JWT tokens' },
385
+ { fromElement: 'OrderService', toElement: 'UserService', description: 'Validates user before placing order' },
386
+ { fromElement: 'UserService', toElement: 'EmailService', description: 'Sends welcome email on signup' },
387
+ ]
388
+
389
+ async function main() {
390
+ try {
391
+ // Get all cross-references originating from 'UserService'
392
+ const userServiceRefs = getCrossRefsForElement('UserService', allCrossRefs)
393
+ console.log('Cross-references for UserService:')
394
+ console.log(userServiceRefs)
395
+ // Output:
396
+ // [
397
+ // { fromElement: 'UserService', toElement: 'AuthService', description: 'Delegates login to AuthService' },
398
+ // { fromElement: 'UserService', toElement: 'UserRepository', description: 'Reads/writes user records' },
399
+ // { fromElement: 'UserService', toElement: 'EmailService', description: 'Sends welcome email on signup' }
400
+ // ]
401
+
402
+ // Element with no outgoing references returns an empty array
403
+ const noRefs = getCrossRefsForElement('PaymentService', allCrossRefs)
404
+ console.log('\nCross-references for PaymentService:', noRefs)
405
+ // Output: []
406
+
407
+ // Practical use: list all "see also" targets for a given element
408
+ const targets = userServiceRefs.map(ref => ref.toElement)
409
+ console.log('\nUserService depends on:', targets)
410
+ // Output: [ 'AuthService', 'UserRepository', 'EmailService' ]
411
+ } catch (error) {
412
+ console.error('Failed to retrieve cross-references:', error)
413
+ }
414
+ }
415
+
416
+ main()
417
+ ```
418
+
419
+ ### `buildNavigation`
420
+
421
+ ```typescript
422
+ function buildNavigation(topics: Topic[]): NavigationItem[]
423
+ ```
424
+
425
+ Use this to convert a flat list of documentation topics into a hierarchical navigation structure suitable for rendering sidebars, menus, or breadcrumbs.
426
+
427
+ Given an array of `Topic` objects (each containing an ID, name, and associated docs), `buildNavigation` returns a nested `NavigationItem[]` where each top-level item maps to a topic and its children map to individual documented elements.
428
+
429
+ ## Parameters
430
+
431
+ | Name | Type | Required | Description |
432
+ |------|------|----------|-------------|
433
+ | `topics` | `Topic[]` | ✅ | Array of topic objects, each with an `id`, `name`, and `docs` array of documented elements |
434
+
435
+ ## Returns
436
+
437
+ Returns `NavigationItem[]` — an array of navigation nodes where:
438
+ - Each **top-level item** has a `title` (from `topic.name`) and a `path` (e.g. `"/utilities"`)
439
+ - Each **child item** represents a documented element within that topic, nested under `children`
440
+
441
+ Returns an **empty array** `[]` when given an empty `topics` input.
442
+
443
+ **Example:**
444
+
445
+ ```typescript example.ts
446
+ // ── Inline types (no external imports needed) ──────────────────────────────
447
+
448
+ type DocElement = {
449
+ name: string;
450
+ description?: string;
451
+ };
452
+
453
+ type GeneratedDoc = {
454
+ element: DocElement;
455
+ markdown: string;
456
+ };
457
+
458
+ type Topic = {
459
+ id: string;
460
+ name: string;
461
+ docs: GeneratedDoc[];
462
+ };
463
+
464
+ type NavigationItem = {
465
+ title: string;
466
+ path: string;
467
+ children?: NavigationItem[];
468
+ };
469
+
470
+ // ── Inline implementation ──────────────────────────────────────────────────
471
+
472
+ function buildNavigation(topics: Topic[]): NavigationItem[] {
473
+ return topics.map(topic => ({
474
+ title: topic.name,
475
+ path: `/${topic.id}`,
476
+ children: topic.docs.map(doc => ({
477
+ title: doc.element.name,
478
+ path: `/${topic.id}/${doc.element.name.toLowerCase().replace(/\s+/g, '-')}`,
479
+ })),
480
+ }));
481
+ }
482
+
483
+ // ── Example usage ──────────────────────────────────────────────────────────
484
+
485
+ const topics: Topic[] = [
486
+ {
487
+ id: 'utilities',
488
+ name: 'Utilities',
489
+ docs: [
490
+ { element: { name: 'formatDate', description: 'Formats a date string' }, markdown: '...' },
491
+ { element: { name: 'slugify', description: 'Converts text to a URL slug' }, markdown: '...' },
492
+ ],
493
+ },
494
+ {
495
+ id: 'auth',
496
+ name: 'Authentication',
497
+ docs: [
498
+ { element: { name: 'createToken', description: 'Issues a signed JWT' }, markdown: '...' },
499
+ { element: { name: 'verifyToken', description: 'Validates a JWT' }, markdown: '...' },
500
+ ],
501
+ },
502
+ ];
503
+
504
+ try {
505
+ const nav = buildNavigation(topics);
506
+ console.log('Navigation structure:\n', JSON.stringify(nav, null, 2));
507
+ /*
508
+ Expected output:
509
+ [
510
+ {
511
+ "title": "Utilities",
512
+ "path": "/utilities",
513
+ "children": [
514
+ { "title": "formatDate", "path": "/utilities/formatdate" },
515
+ { "title": "slugify", "path": "/utilities/slugify" }
516
+ ]
517
+ },
518
+ {
519
+ "title": "Authentication",
520
+ "path": "/auth",
521
+ "children": [
522
+ { "title": "createToken", "path": "/auth/createtoken" },
523
+ { "title": "verifyToken", "path": "/auth/verifytoken" }
524
+ ]
525
+ }
526
+ ]
527
+ */
528
+
529
+ // Edge case: empty topics list
530
+ const emptyNav = buildNavigation([]);
531
+ console.log('Empty input returns:', emptyNav); // []
532
+ } catch (error) {
533
+ console.error('buildNavigation failed:', error);
534
+ }
535
+ ```
536
+
537
+ ### `generateSidebarConfig`
538
+
539
+ ```typescript
540
+ function generateSidebarConfig(topics: Topic[]): object
541
+ ```
542
+
543
+ Use this to convert an array of documentation topics into a sidebar navigation configuration compatible with multiple documentation platforms (Mintlify, Docusaurus, etc.).
544
+
545
+ The function maps each topic to a navigation group, with its documents as slugified page paths in the format `topicId/doc-name`.
546
+
547
+ ## Parameters
548
+
549
+ | Name | Type | Required | Description |
550
+ |------|------|----------|-------------|
551
+ | `topics` | `Topic[]` | ✅ | Array of topic objects, each containing an `id`, `name`, and `docs` array of documented elements |
552
+
553
+ ## Returns
554
+
555
+ Returns a plain `object` with a `navigation` key containing an array of group entries:
556
+
557
+ ```ts
558
+ {
559
+ navigation: Array<{
560
+ group: string // Human-readable topic name
561
+ pages: string[] // Slugified paths in format "topicId/element-name"
562
+ }>
563
+ }
564
+ ```
565
+
566
+ Returns an object with an empty `navigation` array if `topics` is empty.
567
+
568
+ **Example:**
569
+
570
+ ```typescript example.ts
571
+ // ── Inline types (no external imports needed) ──────────────────────────────
572
+ type DocElement = { name: string }
573
+ type GeneratedDoc = { element: DocElement }
574
+ type Topic = {
575
+ id: string
576
+ name: string
577
+ docs: GeneratedDoc[]
578
+ }
579
+
580
+ // ── Inline slugify helper (mirrors the real implementation) ─────────────────
581
+ function slugify(text: string): string {
582
+ return text
583
+ .toLowerCase()
584
+ .replace(/\s+/g, '-')
585
+ .replace(/[^a-z0-9-]/g, '')
586
+ }
587
+
588
+ // ── Inline generateSidebarConfig ────────────────────────────────────────────
589
+ function generateSidebarConfig(topics: Topic[]): object {
590
+ return {
591
+ navigation: topics.map(topic => ({
592
+ group: topic.name,
593
+ pages: topic.docs.map(doc => `${topic.id}/${slugify(doc.element.name)}`)
594
+ }))
595
+ }
596
+ }
597
+
598
+ // ── Realistic example data ──────────────────────────────────────────────────
599
+ const topics: Topic[] = [
600
+ {
601
+ id: 'authentication',
602
+ name: 'Authentication',
603
+ docs: [
604
+ { element: { name: 'createSession' } },
605
+ { element: { name: 'refreshToken' } },
606
+ { element: { name: 'revokeAccess' } },
607
+ ]
608
+ },
609
+ {
610
+ id: 'payments',
611
+ name: 'Payments & Billing',
612
+ docs: [
613
+ { element: { name: 'createCharge' } },
614
+ { element: { name: 'issueRefund' } },
615
+ ]
616
+ },
617
+ {
618
+ id: 'webhooks',
619
+ name: 'Webhooks',
620
+ docs: [
621
+ { element: { name: 'registerEndpoint' } },
622
+ ]
623
+ }
624
+ ]
625
+
626
+ // ── Run it ──────────────────────────────────────────────────────────────────
627
+ try {
628
+ const sidebarConfig = generateSidebarConfig(topics)
629
+ console.log('Sidebar config:', JSON.stringify(sidebarConfig, null, 2))
630
+
631
+ // Expected output:
632
+ // {
633
+ // "navigation": [
634
+ // {
635
+ // "group": "Authentication",
636
+ // "pages": [
637
+ // "authentication/createsession",
638
+ // "authentication/refreshtoken",
639
+ // "authentication/revokeaccess"
640
+ // ]
641
+ // },
642
+ // {
643
+ // "group": "Payments & Billing",
644
+ // "pages": [
645
+ // "payments/createcharge",
646
+ // "payments/issuerefund"
647
+ // ]
648
+ // },
649
+ // {
650
+ // "group": "Webhooks",
651
+ // "pages": [
652
+ // "webhooks/registerendpoint"
653
+ // ]
654
+ // }
655
+ // ]
656
+ // }
657
+
658
+ // Edge case: empty topics array
659
+ const emptyConfig = generateSidebarConfig([])
660
+ console.log('Empty config:', JSON.stringify(emptyConfig))
661
+ // Output: { "navigation": [] }
662
+
663
+ } catch (error) {
664
+ console.error('Failed to generate sidebar config:', error)
665
+ }
666
+ ```
667
+
668
+ ### `mergeTopicConfig`
669
+
670
+ ```typescript
671
+ function mergeTopicConfig(userConfig: Partial<TopicConfig>, defaults: TopicConfig = DEFAULT_TOPIC_CONFIG): TopicConfig
672
+ ```
673
+
674
+ Use this to safely merge a partial user-provided topic configuration with a set of defaults, ensuring the resulting config is always complete and valid.
675
+
676
+ This is useful when users supply only a subset of configuration options (e.g., overriding a few topics) and you need to fill in the rest from sensible defaults without losing any required fields.
677
+
678
+ ### Parameters
679
+
680
+ | Name | Type | Required | Description |
681
+ |------|------|----------|-------------|
682
+ | `userConfig` | `Partial<TopicConfig>` | Yes | The user-supplied configuration. Only the fields provided will override the defaults. |
683
+ | `defaults` | `TopicConfig` | No | The base configuration to fall back on. Defaults to `DEFAULT_TOPIC_CONFIG` if not provided. |
684
+
685
+ ### Returns
686
+
687
+ Returns a complete `TopicConfig` object with all fields populated — user-provided values take precedence over defaults where supplied.
688
+
689
+ **Example:**
690
+
691
+ ```typescript example.ts
692
+ // Inline types (no external imports needed)
693
+ type Topic = {
694
+ label: string
695
+ description: string
696
+ color?: string
697
+ }
698
+
699
+ type TopicConfig = {
700
+ topics: Record<string, Topic>
701
+ }
702
+
703
+ // Simulate DEFAULT_TOPIC_CONFIG
704
+ const DEFAULT_TOPIC_CONFIG: TopicConfig = {
705
+ topics: {
706
+ general: {
707
+ label: 'General',
708
+ description: 'General documentation',
709
+ color: '#gray',
710
+ },
711
+ api: {
712
+ label: 'API Reference',
713
+ description: 'Auto-generated API docs',
714
+ color: '#blue',
715
+ },
716
+ guides: {
717
+ label: 'Guides',
718
+ description: 'Step-by-step tutorials',
719
+ color: '#green',
720
+ },
721
+ },
722
+ }
723
+
724
+ // Simulate mergeTopicConfig
725
+ function mergeTopicConfig(
726
+ userConfig: Partial<TopicConfig>,
727
+ defaults: TopicConfig = DEFAULT_TOPIC_CONFIG
728
+ ): TopicConfig {
729
+ return {
730
+ topics: { ...defaults.topics, ...userConfig.topics },
731
+ }
732
+ }
733
+
734
+ // --- Usage Example ---
735
+
736
+ // User only wants to override the 'api' topic and add a new 'changelog' topic
737
+ const userConfig: Partial<TopicConfig> = {
738
+ topics: {
739
+ api: {
740
+ label: 'API Docs',
741
+ description: 'Customized API reference',
742
+ color: '#purple',
743
+ },
744
+ changelog: {
745
+ label: 'Changelog',
746
+ description: 'Release notes and version history',
747
+ color: '#orange',
748
+ },
749
+ },
750
+ }
751
+
752
+ try {
753
+ const finalConfig = mergeTopicConfig(userConfig)
754
+
755
+ console.log('Merged TopicConfig:')
756
+ console.log(JSON.stringify(finalConfig, null, 2))
757
+ // Expected output:
758
+ // {
759
+ // "topics": {
760
+ // "general": { label: "General", ... }, // from defaults
761
+ // "api": { label: "API Docs", color: "#purple", ... }, // overridden by user
762
+ // "guides": { label: "Guides", ... }, // from defaults
763
+ // "changelog": { label: "Changelog", ... } // added by user
764
+ // }
765
+ // }
766
+
767
+ // Verify override worked
768
+ console.log('\nAPI topic color (should be #purple):', finalConfig.topics.api.color)
769
+
770
+ // Verify defaults are preserved
771
+ console.log('General topic preserved:', finalConfig.topics.general.label)
772
+
773
+ // Verify new topic was added
774
+ console.log('Changelog topic added:', finalConfig.topics.changelog?.label)
775
+ } catch (error) {
776
+ console.error('Failed to merge topic config:', error)
777
+ }
778
+ ```
779
+