skrypt-ai 0.7.0 → 0.8.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.
Files changed (110) hide show
  1. package/dist/auth/index.js +3 -3
  2. package/dist/cli.js +1 -1
  3. package/dist/commands/cron.js +0 -4
  4. package/dist/commands/generate/index.d.ts +3 -0
  5. package/dist/commands/generate/index.js +393 -0
  6. package/dist/commands/generate/scan.d.ts +41 -0
  7. package/dist/commands/generate/scan.js +256 -0
  8. package/dist/commands/generate/verify.d.ts +14 -0
  9. package/dist/commands/generate/verify.js +122 -0
  10. package/dist/commands/generate/write.d.ts +25 -0
  11. package/dist/commands/generate/write.js +120 -0
  12. package/dist/commands/import.js +4 -1
  13. package/dist/commands/llms-txt.js +6 -4
  14. package/dist/config/loader.d.ts +0 -1
  15. package/dist/config/loader.js +1 -1
  16. package/dist/generator/agents-md.d.ts +25 -0
  17. package/dist/generator/agents-md.js +122 -0
  18. package/dist/generator/index.d.ts +2 -0
  19. package/dist/generator/index.js +2 -0
  20. package/dist/generator/mdx-serializer.d.ts +11 -0
  21. package/dist/generator/mdx-serializer.js +135 -0
  22. package/dist/generator/organizer.d.ts +1 -16
  23. package/dist/generator/organizer.js +0 -38
  24. package/dist/generator/writer.js +5 -4
  25. package/dist/llm/proxy-client.d.ts +32 -0
  26. package/dist/llm/proxy-client.js +103 -0
  27. package/dist/scanner/csharp.d.ts +0 -4
  28. package/dist/scanner/csharp.js +9 -49
  29. package/dist/scanner/go.d.ts +0 -3
  30. package/dist/scanner/go.js +8 -35
  31. package/dist/scanner/java.d.ts +0 -4
  32. package/dist/scanner/java.js +9 -49
  33. package/dist/scanner/kotlin.d.ts +0 -3
  34. package/dist/scanner/kotlin.js +6 -33
  35. package/dist/scanner/php.d.ts +0 -10
  36. package/dist/scanner/php.js +11 -55
  37. package/dist/scanner/ruby.d.ts +0 -3
  38. package/dist/scanner/ruby.js +8 -38
  39. package/dist/scanner/rust.d.ts +0 -3
  40. package/dist/scanner/rust.js +10 -37
  41. package/dist/scanner/swift.d.ts +0 -3
  42. package/dist/scanner/swift.js +8 -35
  43. package/dist/scanner/utils.d.ts +41 -0
  44. package/dist/scanner/utils.js +97 -0
  45. package/dist/template/docs.json +5 -2
  46. package/dist/template/next.config.mjs +31 -0
  47. package/dist/template/package.json +5 -3
  48. package/dist/template/src/app/layout.tsx +13 -13
  49. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  50. package/dist/template/src/app/llms.txt/route.ts +29 -0
  51. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  52. package/dist/template/src/app/reference/route.ts +22 -18
  53. package/dist/template/src/app/sitemap.ts +1 -1
  54. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  55. package/dist/template/src/components/ai-chat.tsx +20 -193
  56. package/dist/template/src/components/mdx/index.tsx +27 -4
  57. package/dist/template/src/lib/fonts.ts +135 -0
  58. package/dist/template/src/middleware.ts +101 -0
  59. package/dist/template/src/styles/globals.css +28 -20
  60. package/dist/utils/files.d.ts +0 -8
  61. package/dist/utils/files.js +0 -33
  62. package/package.json +1 -1
  63. package/dist/autofix/autofix.test.d.ts +0 -1
  64. package/dist/autofix/autofix.test.js +0 -487
  65. package/dist/commands/generate.d.ts +0 -9
  66. package/dist/commands/generate.js +0 -739
  67. package/dist/generator/generator.test.d.ts +0 -1
  68. package/dist/generator/generator.test.js +0 -259
  69. package/dist/generator/writer.test.d.ts +0 -1
  70. package/dist/generator/writer.test.js +0 -411
  71. package/dist/llm/llm.manual-test.d.ts +0 -1
  72. package/dist/llm/llm.manual-test.js +0 -112
  73. package/dist/llm/llm.mock-test.d.ts +0 -4
  74. package/dist/llm/llm.mock-test.js +0 -79
  75. package/dist/plugins/index.d.ts +0 -47
  76. package/dist/plugins/index.js +0 -181
  77. package/dist/scanner/content-type.test.d.ts +0 -1
  78. package/dist/scanner/content-type.test.js +0 -231
  79. package/dist/scanner/integration.test.d.ts +0 -4
  80. package/dist/scanner/integration.test.js +0 -180
  81. package/dist/scanner/scanner.test.d.ts +0 -1
  82. package/dist/scanner/scanner.test.js +0 -210
  83. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  84. package/dist/scanner/typescript.manual-test.js +0 -112
  85. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  86. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  87. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  88. package/dist/template/src/app/docs/config/page.mdx +0 -428
  89. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  90. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  91. package/dist/template/src/app/docs/generator/generator.md +0 -504
  92. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  93. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  94. package/dist/template/src/app/docs/github/page.mdx +0 -502
  95. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  96. package/dist/template/src/app/docs/llm/index.md +0 -471
  97. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  98. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  99. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  100. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  101. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  102. package/dist/template/src/app/docs/scanner/index.md +0 -212
  103. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  104. package/dist/template/src/app/docs/scanner/python.md +0 -469
  105. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  106. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  107. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  108. package/dist/template/src/app/icon.tsx +0 -29
  109. package/dist/utils/validation.d.ts +0 -1
  110. package/dist/utils/validation.js +0 -12
@@ -1,1793 +0,0 @@
1
- ## Functions
2
-
3
- ### `definePlugin`
4
-
5
- ```typescript
6
- function definePlugin(plugin: SkryptPlugin): SkryptPlugin
7
- ```
8
-
9
- Use this to define a type-safe plugin configuration object for the Skrypt plugin system. This is an identity function that provides TypeScript type inference and autocompletion when authoring plugins, ensuring your plugin object conforms to the `SkryptPlugin` interface at compile time.
10
-
11
- ### Parameters
12
-
13
- | Name | Type | Required | Description |
14
- |------|------|----------|-------------|
15
- | plugin | `SkryptPlugin` | ✅ | The plugin configuration object to validate and return |
16
-
17
- #### Returns
18
-
19
- Returns the same `SkryptPlugin` object passed in, unchanged. The value is identical to the input — the benefit is purely type-safety and IDE autocompletion during authoring.
20
-
21
- **Example:**
22
-
23
- ```typescript example.ts
24
- // Inline the SkryptPlugin type (do not import from autodocs)
25
- type SkryptPlugin = {
26
- name: string
27
- version?: string
28
- setup?: (context: Record<string, unknown>) => void | Promise<void>
29
- teardown?: () => void | Promise<void>
30
- hooks?: {
31
- beforeRun?: () => void
32
- afterRun?: (result: unknown) => void
33
- }
34
- }
35
-
36
- // Inline the definePlugin function
37
- function definePlugin(plugin: SkryptPlugin): SkryptPlugin {
38
- return plugin
39
- }
40
-
41
- // Define a plugin with full type safety and autocompletion
42
- const myPlugin = definePlugin({
43
- name: 'my-analytics-plugin',
44
- version: '1.0.0',
45
-
46
- setup: async (context) => {
47
- const apiKey = process.env.ANALYTICS_API_KEY || 'sk-analytics-abc123'
48
- console.log(`[${myPlugin.name}] Setting up with key: ${apiKey.slice(0, 8)}...`)
49
- context.initialized = true
50
- },
51
-
52
- teardown: async () => {
53
- console.log(`[${myPlugin.name}] Tearing down, releasing resources...`)
54
- },
55
-
56
- hooks: {
57
- beforeRun: () => {
58
- console.log(`[${myPlugin.name}] Hook: before run triggered`)
59
- },
60
- afterRun: (result) => {
61
- console.log(`[${myPlugin.name}] Hook: after run triggered with result:`, result)
62
- },
63
- },
64
- })
65
-
66
- async function main() {
67
- try {
68
- console.log('Plugin defined:', myPlugin.name, `v${myPlugin.version}`)
69
- // Output: Plugin defined: my-analytics-plugin v1.0.0
70
-
71
- const context: Record<string, unknown> = {}
72
- await myPlugin.setup?.(context)
73
- // Output: [my-analytics-plugin] Setting up with key: sk-analy...
74
-
75
- myPlugin.hooks?.beforeRun?.()
76
- // Output: [my-analytics-plugin] Hook: before run triggered
77
-
78
- myPlugin.hooks?.afterRun?.({ status: 'success', records: 42 })
79
- // Output: [my-analytics-plugin] Hook: after run triggered with result: { status: 'success', records: 42 }
80
-
81
- await myPlugin.teardown?.()
82
- // Output: [my-analytics-plugin] Tearing down, releasing resources...
83
-
84
- // definePlugin returns the exact same object reference
85
- console.log('Same reference:', definePlugin(myPlugin) === myPlugin)
86
- // Output: Same reference: true
87
- } catch (error) {
88
- console.error('Plugin error:', error)
89
- }
90
- }
91
-
92
- main()
93
- ```
94
-
95
- ## Classes
96
-
97
- ### `PluginManager`
98
-
99
- ```typescript
100
- class PluginManager
101
- ```
102
-
103
- Use this to load, register, and execute documentation plugins that transform or extend your build pipeline's source and output processing.
104
-
105
- `PluginManager` orchestrates a collection of `SkryptPlugin` instances, providing each plugin with a shared context (paths, config, and a logger) so plugins can coordinate without direct coupling.
106
-
107
- ## Constructor Parameters
108
-
109
- | Name | Type | Required | Description |
110
- |------|------|----------|-------------|
111
- | `sourcePath` | `string` | Yes | Absolute or relative path to the source files directory |
112
- | `outputPath` | `string` | Yes | Absolute or relative path where processed output will be written |
113
- | `config` | `Record<string, unknown>` | No | Arbitrary key-value config passed to every plugin via context. Defaults to `{}` |
114
-
115
- ## Plugin Context
116
-
117
- Every registered plugin receives a shared context object:
118
-
119
- | Field | Type | Description |
120
- |-------|------|-------------|
121
- | `sourcePath` | `string` | The source directory path |
122
- | `outputPath` | `string` | The output directory path |
123
- | `config` | `Record<string, unknown>` | The config passed to the constructor |
124
- | `logger.info` | `(msg: string) => void` | Logs an info message prefixed with `[plugin]` |
125
- | `logger.warn` | `(msg: string) => void` | Logs a warning prefixed with `[plugin]` |
126
- | `logger.error` | `(msg: string) => void` | Logs an error prefixed with `[plugin]` |
127
-
128
- #### Returns
129
-
130
- `new PluginManager(...)` returns a `PluginManager` instance ready to accept plugin registrations and execute them against the shared context.
131
-
132
- **Example:**
133
-
134
- ```typescript example.ts
135
- // --- Inline types (no external imports needed) ---
136
-
137
- type PluginContext = {
138
- sourcePath: string
139
- outputPath: string
140
- config: Record<string, unknown>
141
- logger: {
142
- info: (msg: string) => void
143
- warn: (msg: string) => void
144
- error: (msg: string) => void
145
- }
146
- }
147
-
148
- type SkryptPlugin = {
149
- name: string
150
- setup?: (context: PluginContext) => void | Promise<void>
151
- teardown?: (context: PluginContext) => void | Promise<void>
152
- }
153
-
154
- // --- Self-contained PluginManager implementation ---
155
-
156
- class PluginManager {
157
- private plugins: SkryptPlugin[] = []
158
- private context: PluginContext
159
-
160
- constructor(
161
- sourcePath: string,
162
- outputPath: string,
163
- config: Record<string, unknown> = {}
164
- ) {
165
- this.context = {
166
- sourcePath,
167
- outputPath,
168
- config,
169
- logger: {
170
- info: (msg) => console.log(`[plugin] INFO ${msg}`),
171
- warn: (msg) => console.log(`[plugin] WARN ${msg}`),
172
- error: (msg) => console.log(`[plugin] ERROR ${msg}`),
173
- },
174
- }
175
- }
176
-
177
- register(plugin: SkryptPlugin): void {
178
- this.plugins.push(plugin)
179
- this.context.logger.info(`Registered plugin: ${plugin.name}`)
180
- }
181
-
182
- async runSetup(): Promise<void> {
183
- for (const plugin of this.plugins) {
184
- if (plugin.setup) {
185
- this.context.logger.info(`Running setup for: ${plugin.name}`)
186
- await plugin.setup(this.context)
187
- }
188
- }
189
- }
190
-
191
- async runTeardown(): Promise<void> {
192
- for (const plugin of this.plugins) {
193
- if (plugin.teardown) {
194
- this.context.logger.info(`Running teardown for: ${plugin.name}`)
195
- await plugin.teardown(this.context)
196
- }
197
- }
198
- }
199
- }
200
-
201
- // --- Usage example ---
202
-
203
- const markdownPlugin: SkryptPlugin = {
204
- name: 'markdown-transformer',
205
- setup: async (ctx) => {
206
- ctx.logger.info(`Reading from: ${ctx.sourcePath}`)
207
- ctx.logger.info(`Writing to: ${ctx.outputPath}`)
208
-
209
- const theme = ctx.config.theme ?? 'default'
210
- ctx.logger.info(`Applying theme: ${theme}`)
211
-
212
- // Simulate async file processing
213
- await new Promise((resolve) => setTimeout(resolve, 10))
214
- ctx.logger.info('Markdown transformation complete')
215
- },
216
- teardown: async (ctx) => {
217
- ctx.logger.info('Cleaning up temporary markdown assets')
218
- },
219
- }
220
-
221
- const analyticsPlugin: SkryptPlugin = {
222
- name: 'analytics-injector',
223
- setup: async (ctx) => {
224
- const trackingId = ctx.config.trackingId
225
- if (!trackingId) {
226
- ctx.logger.warn('No trackingId provided — analytics will be skipped')
227
- return
228
- }
229
- ctx.logger.info(`Injecting analytics with ID: ${trackingId}`)
230
- },
231
- }
232
-
233
- async function main() {
234
- try {
235
- const manager = new PluginManager(
236
- process.env.SOURCE_PATH || './src/docs',
237
- process.env.OUTPUT_PATH || './dist/docs',
238
- {
239
- theme: process.env.DOCS_THEME || 'dark',
240
- trackingId: process.env.ANALYTICS_ID || '', // intentionally empty to demo warn
241
- }
242
- )
243
-
244
- manager.register(markdownPlugin)
245
- manager.register(analyticsPlugin)
246
-
247
- console.log('\n--- Running plugin setup ---')
248
- await manager.runSetup()
249
-
250
- console.log('\n--- Running plugin teardown ---')
251
- await manager.runTeardown()
252
-
253
- console.log('\nDone.')
254
- /*
255
- Expected output:
256
- [plugin] INFO Registered plugin: markdown-transformer
257
- [plugin] INFO Registered plugin: analytics-injector
258
-
259
- --- Running plugin setup ---
260
- [plugin] INFO Running setup for: markdown-transformer
261
- [plugin] INFO Reading from: ./src/docs
262
- [plugin] INFO Writing to: ./dist/docs
263
- [plugin] INFO Applying theme: dark
264
- [plugin] INFO Markdown transformation complete
265
- [plugin] INFO Running setup for: analytics-injector
266
- [plugin] WARN No trackingId provided — analytics will be skipped
267
-
268
- --- Running plugin teardown ---
269
- [plugin] INFO Running teardown for: markdown-transformer
270
- [plugin] INFO Cleaning up temporary markdown assets
271
-
272
- Done.
273
- */
274
- } catch (error) {
275
- console.error('PluginManager failed:', error)
276
- process.exit(1)
277
- }
278
- }
279
-
280
- main()
281
- ```
282
-
283
- ### Methods
284
-
285
- #### `constructor`
286
-
287
- ```typescript
288
- constructor(sourcePath: string, outputPath: string, config: Record<string, unknown> = {})
289
- ```
290
-
291
- Use this to initialize a `PluginManager` that coordinates documentation plugins, wiring together your source code directory, output destination, and any custom configuration options before plugins are registered and executed.
292
-
293
- #### Parameters
294
-
295
- | Name | Type | Required | Description |
296
- |------|------|----------|-------------|
297
- | `sourcePath` | `string` | ✅ Yes | Absolute or relative path to the source code directory to be documented |
298
- | `outputPath` | `string` | ✅ Yes | Absolute or relative path to the directory where generated docs will be written |
299
- | `config` | `Record<string, unknown>` | ❌ No | Optional key-value configuration passed to all plugins via shared context (defaults to `{}`) |
300
-
301
- #### Returns
302
-
303
- A `PluginManager` instance with an initialized plugin context. The internal `plugins` array starts empty — use the instance's registration methods to add plugins before running them.
304
-
305
- ## Notes
306
-
307
- - `sourcePath` and `outputPath` are stored in the shared `PluginContext`, making them accessible to every registered plugin
308
- - Passing `config` lets you forward options like theme settings, version strings, or feature flags to all plugins without coupling them directly
309
- - The logger is set up automatically during construction — plugins receive it via context without additional setup
310
-
311
- **Example:**
312
-
313
- ```typescript example.ts
314
- // --- Inline types (mirroring autodocs internals) ---
315
- type Logger = {
316
- info: (msg: string) => void
317
- warn: (msg: string) => void
318
- error: (msg: string) => void
319
- }
320
-
321
- type PluginContext = {
322
- sourcePath: string
323
- outputPath: string
324
- config: Record<string, unknown>
325
- logger: Logger
326
- }
327
-
328
- type SkryptPlugin = {
329
- name: string
330
- run: (context: PluginContext) => Promise<void>
331
- }
332
-
333
- // --- Self-contained PluginManager implementation ---
334
- class PluginManager {
335
- private plugins: SkryptPlugin[] = []
336
- private context: PluginContext
337
-
338
- constructor(
339
- sourcePath: string,
340
- outputPath: string,
341
- config: Record<string, unknown> = {}
342
- ) {
343
- this.context = {
344
- sourcePath,
345
- outputPath,
346
- config,
347
- logger: {
348
- info: (msg) => console.log(`[INFO] ${msg}`),
349
- warn: (msg) => console.warn(`[WARN] ${msg}`),
350
- error: (msg) => console.error(`[ERROR] ${msg}`),
351
- },
352
- }
353
- }
354
-
355
- register(plugin: SkryptPlugin): void {
356
- this.plugins.push(plugin)
357
- this.context.logger.info(`Registered plugin: "${plugin.name}"`)
358
- }
359
-
360
- async runAll(): Promise<void> {
361
- for (const plugin of this.plugins) {
362
- this.context.logger.info(`Running plugin: "${plugin.name}"`)
363
- await plugin.run(this.context)
364
- }
365
- }
366
-
367
- getContext(): PluginContext {
368
- return this.context
369
- }
370
- }
371
-
372
- // --- Usage example ---
373
- async function main() {
374
- try {
375
- const manager = new PluginManager(
376
- './src', // sourcePath
377
- './docs/output', // outputPath
378
- { // optional config
379
- version: '2.1.0',
380
- theme: 'dark',
381
- includePrivate: false,
382
- }
383
- )
384
-
385
- // Inspect the initialized context
386
- const ctx = manager.getContext()
387
- console.log('PluginManager initialized:')
388
- console.log(' sourcePath:', ctx.sourcePath)
389
- console.log(' outputPath:', ctx.outputPath)
390
- console.log(' config: ', ctx.config)
391
- // Output:
392
- // PluginManager initialized:
393
- // sourcePath: ./src
394
- // outputPath: ./docs/output
395
- // config: { version: '2.1.0', theme: 'dark', includePrivate: false }
396
-
397
- // Register and run a sample plugin to verify context is passed correctly
398
- manager.register({
399
- name: 'markdown-emitter',
400
- run: async (context) => {
401
- context.logger.info(
402
- `Emitting docs from "${context.sourcePath}" → "${context.outputPath}" ` +
403
- `(v${context.config.version})`
404
- )
405
- },
406
- })
407
-
408
- await manager.runAll()
409
- // Output:
410
- // [INFO] Registered plugin: "markdown-emitter"
411
- // [INFO] Running plugin: "markdown-emitter"
412
- // [INFO] Emitting docs from "./src" → "./docs/output" (v2.1.0)
413
-
414
- } catch (error) {
415
- console.error('PluginManager setup failed:', error)
416
- }
417
- }
418
-
419
- main()
420
- ```
421
-
422
- #### `loadPlugins`
423
-
424
- ```typescript
425
- async loadPlugins(configPath?: string): Promise<void>
426
- ```
427
-
428
- Use this to initialize and load plugins into a `PluginManager` instance from a configuration file, enabling dynamic plugin discovery at startup.
429
-
430
- This method reads from a `skrypt.config.js` or `skrypt.config.ts` file (auto-detected if no path is provided) and registers all declared plugins into the manager. Call it once during application bootstrap before executing any plugin-dependent logic.
431
-
432
- ### Parameters
433
-
434
- | Name | Type | Required | Description |
435
- |------|------|----------|-------------|
436
- | `configPath` | `string` | No | Absolute or relative path to a config file. If omitted, the manager searches for `skrypt.config.js` or `skrypt.config.ts` in the current working directory. |
437
-
438
- #### Returns
439
-
440
- Returns `Promise<void>`. Resolves when all plugins have been loaded. Resolves silently (no error thrown) if no config file is found — check your logs if plugins appear missing.
441
-
442
- ### Behavior Notes
443
-
444
- - **No config file found** → resolves immediately without loading anything
445
- - **Config file found** → loads and registers all plugins declared in the file
446
- - **Invalid config** → may throw or log errors depending on the plugin's implementation
447
-
448
- **Example:**
449
-
450
- ```typescript example.ts
451
- // Inline types to simulate PluginManager behavior
452
- type Logger = {
453
- info: (msg: string) => void
454
- error: (msg: string) => void
455
- }
456
-
457
- type Plugin = {
458
- name: string
459
- setup: () => void
460
- }
461
-
462
- type PluginConfig = {
463
- plugins: Plugin[]
464
- }
465
-
466
- // Simulated PluginManager class (self-contained, no external imports)
467
- class PluginManager {
468
- private plugins: Plugin[] = []
469
- private logger: Logger
470
-
471
- constructor(logger?: Logger) {
472
- this.logger = logger ?? {
473
- info: (msg) => console.log(`[plugin] ${msg}`),
474
- error: (msg) => console.error(`[plugin] ${msg}`),
475
- }
476
- }
477
-
478
- // Simulates finding skrypt.config.js / skrypt.config.ts
479
- private findConfigFile(): string | null {
480
- const candidates = ['skrypt.config.js', 'skrypt.config.ts']
481
- // In a real implementation this uses existsSync from 'fs'
482
- // Here we simulate a found config for demonstration
483
- console.log(`[plugin] Searching for config: ${candidates.join(', ')}`)
484
- return process.env.PLUGIN_CONFIG_PATH || null
485
- }
486
-
487
- async loadPlugins(configPath?: string): Promise<void> {
488
- const configFile = configPath || this.findConfigFile()
489
-
490
- if (!configFile) {
491
- this.logger.info('No config file found — skipping plugin load')
492
- return
493
- }
494
-
495
- try {
496
- this.logger.info(`Loading plugins from: ${configFile}`)
497
-
498
- // Simulate loading a config module
499
- const config: PluginConfig = await this.simulateConfigLoad(configFile)
500
-
501
- for (const plugin of config.plugins) {
502
- plugin.setup()
503
- this.plugins.push(plugin)
504
- this.logger.info(`Loaded plugin: ${plugin.name}`)
505
- }
506
-
507
- this.logger.info(`Total plugins loaded: ${this.plugins.length}`)
508
- } catch (err) {
509
- this.logger.error(`Failed to load plugins: ${(err as Error).message}`)
510
- throw err
511
- }
512
- }
513
-
514
- // Simulates dynamic import of a config file
515
- private async simulateConfigLoad(_path: string): Promise<PluginConfig> {
516
- return {
517
- plugins: [
518
- { name: 'markdown-plugin', setup: () => console.log(' → markdown-plugin initialized') },
519
- { name: 'auth-plugin', setup: () => console.log(' → auth-plugin initialized') },
520
- ],
521
- }
522
- }
523
-
524
- getLoadedPlugins(): string[] {
525
- return this.plugins.map((p) => p.name)
526
- }
527
- }
528
-
529
- // --- Usage ---
530
- async function main() {
531
- const manager = new PluginManager()
532
-
533
- try {
534
- // Option 1: Auto-detect config file (uses PLUGIN_CONFIG_PATH env or returns null)
535
- await manager.loadPlugins()
536
-
537
- // Option 2: Provide an explicit config path
538
- await manager.loadPlugins(
539
- process.env.PLUGIN_CONFIG_PATH || './skrypt.config.js'
540
- )
541
-
542
- console.log('\nLoaded plugins:', manager.getLoadedPlugins())
543
- // Output:
544
- // [plugin] Loading plugins from: ./skrypt.config.js
545
- // → markdown-plugin initialized
546
- // → auth-plugin initialized
547
- // [plugin] Total plugins loaded: 2
548
- // Loaded plugins: [ 'markdown-plugin', 'auth-plugin' ]
549
- } catch (error) {
550
- console.error('Plugin initialization failed:', error)
551
- process.exit(1)
552
- }
553
- }
554
-
555
- main()
556
- ```
557
-
558
- #### `register`
559
-
560
- ```typescript
561
- register(plugin: SkryptPlugin): void
562
- ```
563
-
564
- Use this to manually register a plugin with the `PluginManager`, adding it to the active plugin list so its hooks can be executed during the build or runtime lifecycle.
565
-
566
- This is the programmatic alternative to loading plugins from disk — ideal for built-in plugins, testing, or dynamically constructed plugin objects.
567
-
568
- ### Parameters
569
-
570
- | Name | Type | Required | Description |
571
- |------|------|----------|-------------|
572
- | `plugin` | `SkryptPlugin` | ✅ Yes | The plugin object to register. Must have at least a `name` property; `version` is optional but recommended. |
573
-
574
- #### Returns
575
-
576
- `void` — Registers the plugin in-place. After calling `register()`, the plugin is immediately available for hook execution via `runHook()`.
577
-
578
- ### Behavior Notes
579
- - Plugins are stored in insertion order and hooks are run in that order.
580
- - A confirmation log is printed: `Loaded plugin: <name> v<version>` (version omitted if not provided).
581
- - No deduplication is performed — registering the same plugin twice will add it twice.
582
-
583
- **Example:**
584
-
585
- ```typescript example.ts
586
- // Inline type definitions (no external imports needed)
587
- type SkryptPlugin = {
588
- name: string
589
- version?: string
590
- onBuildStart?: (...args: unknown[]) => unknown
591
- onBuildEnd?: (...args: unknown[]) => unknown
592
- }
593
-
594
- // Inline PluginManager implementation
595
- class PluginManager {
596
- private plugins: SkryptPlugin[] = []
597
-
598
- register(plugin: SkryptPlugin): void {
599
- this.plugins.push(plugin)
600
- console.log(
601
- `Loaded plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`
602
- )
603
- }
604
-
605
- async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined> {
606
- for (const plugin of this.plugins) {
607
- const fn = plugin[hook]
608
- if (typeof fn === 'function') {
609
- await fn(...args)
610
- }
611
- }
612
- return undefined
613
- }
614
-
615
- getPlugins(): SkryptPlugin[] {
616
- return [...this.plugins]
617
- }
618
- }
619
-
620
- // --- Usage Example ---
621
-
622
- const manager = new PluginManager()
623
-
624
- // Plugin with version
625
- const analyticsPlugin: SkryptPlugin = {
626
- name: 'analytics-tracker',
627
- version: '2.1.0',
628
- onBuildStart: () => console.log(' [analytics] Build started, initializing trackers...'),
629
- onBuildEnd: () => console.log(' [analytics] Build complete, flushing events.'),
630
- }
631
-
632
- // Plugin without version (version log is omitted)
633
- const minifierPlugin: SkryptPlugin = {
634
- name: 'html-minifier',
635
- onBuildEnd: () => console.log(' [minifier] Minifying HTML output...'),
636
- }
637
-
638
- async function main() {
639
- try {
640
- // Register plugins manually
641
- manager.register(analyticsPlugin)
642
- // Output: Loaded plugin: analytics-tracker v2.1.0
643
-
644
- manager.register(minifierPlugin)
645
- // Output: Loaded plugin: html-minifier
646
-
647
- console.log(`\nRegistered plugins: ${manager.getPlugins().map(p => p.name).join(', ')}`)
648
- // Output: Registered plugins: analytics-tracker, html-minifier
649
-
650
- // Trigger hooks to confirm plugins are active
651
- console.log('\n-- Running onBuildStart --')
652
- await manager.runHook('onBuildStart')
653
-
654
- console.log('\n-- Running onBuildEnd --')
655
- await manager.runHook('onBuildEnd')
656
-
657
- } catch (error) {
658
- console.error('Plugin registration failed:', error)
659
- }
660
- }
661
-
662
- main()
663
- ```
664
-
665
- #### `runHook`
666
-
667
- ```typescript
668
- async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined>
669
- ```
670
-
671
- Use this to execute a named hook across all registered plugins in sequence, passing data through each plugin's implementation of that hook. This enables a plugin pipeline where each plugin can transform or augment the result before passing it to the next.
672
-
673
- **Parameters**
674
-
675
- | Name | Type | Required | Description |
676
- |------|------|----------|-------------|
677
- | `hook` | `keyof SkryptPlugin` | Yes | The name of the hook method to invoke on each registered plugin (e.g., `'beforeCompile'`, `'afterCompile'`) |
678
- | `...args` | `unknown[]` | No | Arguments passed to each plugin's hook function. The first argument (`args[0]`) serves as the initial result value that gets threaded through the pipeline |
679
-
680
- **Returns**
681
-
682
- | Condition | Return Value |
683
- |-----------|-------------|
684
- | One or more plugins implement the hook | `Promise<T>` — the final transformed result after all plugins have run |
685
- | No plugins implement the hook | `Promise<undefined>` — resolves with the initial `args[0]` value cast as `T`, or `undefined` if no args provided |
686
-
687
- The return value is the **accumulated result** after all plugins have had a chance to process it — plugins run in registration order.
688
-
689
- **Example:**
690
-
691
- ```typescript example.ts
692
- // --- Inline types (do not import from autodocs) ---
693
- interface SkryptPlugin {
694
- name: string
695
- version?: string
696
- beforeCompile?: (...args: unknown[]) => Promise<string> | string
697
- afterCompile?: (...args: unknown[]) => Promise<string> | string
698
- transform?: (...args: unknown[]) => Promise<string> | string
699
- }
700
-
701
- // --- Inline PluginManager implementation ---
702
- class PluginManager {
703
- private plugins: SkryptPlugin[] = []
704
-
705
- register(plugin: SkryptPlugin): void {
706
- this.plugins.push(plugin)
707
- console.log(
708
- `Loaded plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`
709
- )
710
- }
711
-
712
- async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined> {
713
- let result = args[0] as T
714
-
715
- for (const plugin of this.plugins) {
716
- const fn = plugin[hook] as ((...args: unknown[]) => Promise<T> | T) | undefined
717
- if (typeof fn === 'function') {
718
- result = await fn.call(plugin, result, ...args.slice(1))
719
- }
720
- }
721
-
722
- return result
723
- }
724
- }
725
-
726
- // --- Example: transform source code through a plugin pipeline ---
727
- const manager = new PluginManager()
728
-
729
- // Plugin 1: strips comments
730
- manager.register({
731
- name: 'strip-comments',
732
- version: '1.0.0',
733
- transform: (source: unknown) => {
734
- return (source as string).replace(/\/\/.*$/gm, '').trim()
735
- },
736
- })
737
-
738
- // Plugin 2: minifies whitespace
739
- manager.register({
740
- name: 'minify-whitespace',
741
- version: '2.1.3',
742
- transform: (source: unknown) => {
743
- return (source as string).replace(/\s+/g, ' ').trim()
744
- },
745
- })
746
-
747
- // Plugin 3: only implements 'afterCompile' — skipped during 'transform' hook
748
- manager.register({
749
- name: 'logger-plugin',
750
- afterCompile: (source: unknown) => {
751
- console.log('[logger-plugin] Compilation finished.')
752
- return source as string
753
- },
754
- })
755
-
756
- async function main() {
757
- const rawSource = `
758
- // This is a comment
759
- const x = 42;
760
- // Another comment
761
- const y = x + 1;
762
- `
763
-
764
- try {
765
- // Run the 'transform' hook — only plugins that define it will participate
766
- const transformed = await manager.runHook<string>('transform', rawSource)
767
- console.log('Transformed source:', transformed)
768
- // Output: "const x = 42; const y = x + 1;"
769
-
770
- // Run the 'afterCompile' hook — only logger-plugin participates
771
- const afterResult = await manager.runHook<string>('afterCompile', transformed)
772
- console.log('After compile result:', afterResult)
773
- // Output: [logger-plugin] Compilation finished.
774
- // "const x = 42; const y = x + 1;"
775
-
776
- // Run a hook with no implementations — returns initial value unchanged
777
- const noOp = await manager.runHook<string>('beforeCompile', 'original code')
778
- console.log('No-op hook result:', noOp)
779
- // Output: "original code"
780
- } catch (error) {
781
- console.error('Hook execution failed:', error)
782
- }
783
- }
784
-
785
- main()
786
- ```
787
-
788
- #### `onInit`
789
-
790
- ```typescript
791
- async onInit(): Promise<void>
792
- ```
793
-
794
- Use this to trigger the initialization lifecycle hook across all registered plugins in a `PluginManager` instance — typically called once at application startup before any scanning or processing begins.
795
-
796
- When invoked, `onInit` runs the `'onInit'` hook on every registered plugin that implements it, allowing plugins to set up connections, load configuration, or prepare internal state.
797
-
798
- #### Parameters
799
-
800
- This method takes no parameters.
801
-
802
- #### Returns
803
-
804
- | Type | Description |
805
- |------|-------------|
806
- | `Promise<void>` | Resolves when all plugin `onInit` hooks have completed. Rejects if any plugin's hook throws. |
807
-
808
- ## Behavior Notes
809
-
810
- - Execution is **sequential** — plugins are initialized in registration order.
811
- - If a plugin does **not** implement `onInit`, it is silently skipped.
812
- - A failure in one plugin's `onInit` will **halt** the chain and reject the promise.
813
-
814
- **Example:**
815
-
816
- ```typescript example.ts
817
- // Inline types — no external imports needed
818
- type PluginHook = () => Promise<void>
819
-
820
- interface Plugin {
821
- name: string
822
- onInit?: PluginHook
823
- onBeforeScan?: PluginHook
824
- }
825
-
826
- // Simulated PluginManager class matching the real implementation's behavior
827
- class PluginManager {
828
- private plugins: Plugin[] = []
829
-
830
- register(plugin: Plugin): void {
831
- this.plugins.push(plugin)
832
- console.log(`[PluginManager] Registered plugin: ${plugin.name}`)
833
- }
834
-
835
- private async runHook(hookName: keyof Plugin): Promise<void> {
836
- for (const plugin of this.plugins) {
837
- const hook = plugin[hookName]
838
- if (typeof hook === 'function') {
839
- console.log(`[PluginManager] Running '${hookName}' for plugin: ${plugin.name}`)
840
- await (hook as PluginHook).call(plugin)
841
- } else {
842
- console.log(`[PluginManager] Plugin '${plugin.name}' has no '${hookName}' hook — skipping`)
843
- }
844
- }
845
- }
846
-
847
- async onInit(): Promise<void> {
848
- await this.runHook('onInit')
849
- }
850
- }
851
-
852
- // Example plugins — one with onInit, one without
853
- const databasePlugin: Plugin = {
854
- name: 'DatabasePlugin',
855
- onInit: async () => {
856
- const dbUrl = process.env.DATABASE_URL || 'postgres://localhost:5432/mydb'
857
- console.log(` → Connecting to database at: ${dbUrl}`)
858
- // Simulate async connection setup
859
- await new Promise((resolve) => setTimeout(resolve, 50))
860
- console.log(' → Database connection established')
861
- },
862
- }
863
-
864
- const loggerPlugin: Plugin = {
865
- name: 'LoggerPlugin',
866
- // No onInit — will be skipped gracefully
867
- }
868
-
869
- const cachePlugin: Plugin = {
870
- name: 'CachePlugin',
871
- onInit: async () => {
872
- const cacheHost = process.env.CACHE_HOST || 'redis://localhost:6379'
873
- console.log(` → Initializing cache at: ${cacheHost}`)
874
- await new Promise((resolve) => setTimeout(resolve, 30))
875
- console.log(' → Cache ready')
876
- },
877
- }
878
-
879
- async function main() {
880
- const manager = new PluginManager()
881
-
882
- manager.register(databasePlugin)
883
- manager.register(loggerPlugin)
884
- manager.register(cachePlugin)
885
-
886
- console.log('\n--- Running onInit lifecycle hook ---')
887
-
888
- try {
889
- await manager.onInit()
890
- console.log('\n✓ All plugins initialized successfully')
891
- // Expected output:
892
- // [PluginManager] Running 'onInit' for plugin: DatabasePlugin
893
- // → Connecting to database at: postgres://localhost:5432/mydb
894
- // → Database connection established
895
- // [PluginManager] Plugin 'LoggerPlugin' has no 'onInit' hook — skipping
896
- // [PluginManager] Running 'onInit' for plugin: CachePlugin
897
- // → Initializing cache at: redis://localhost:6379
898
- // → Cache ready
899
- // ✓ All plugins initialized successfully
900
- } catch (error) {
901
- console.error('✗ Plugin initialization failed:', error)
902
- process.exit(1)
903
- }
904
- }
905
-
906
- main()
907
- ```
908
-
909
- #### `onBeforeScan`
910
-
911
- ```typescript
912
- async onBeforeScan(): Promise<void>
913
- ```
914
-
915
- Use this to trigger all registered plugins' `onBeforeScan` lifecycle hook — ideal for running pre-scan setup logic (e.g., clearing caches, validating config, preparing output directories) before your autodocs scan begins.
916
-
917
- This method iterates over every loaded plugin and calls their `onBeforeScan` handler in sequence. It resolves when all hooks have completed, making it safe to `await` before starting a scan.
918
-
919
- ### Parameters
920
-
921
- _None_
922
-
923
- #### Returns
924
-
925
- | Type | Description |
926
- |------|-------------|
927
- | `Promise<void>` | Resolves when all registered `onBeforeScan` plugin hooks have completed. Does not return a value. |
928
-
929
- #### When to Call It
930
-
931
- Call `onBeforeScan` **after** `onInit` and **before** you begin scanning source files. The typical lifecycle order is:
932
-
933
- 1. `onInit()` — plugin initialization
934
- 2. `onBeforeScan()` — pre-scan preparation ← **this method**
935
- 3. _(scan runs)_
936
- 4. `onAfterScan(elements)` — post-scan transformation
937
-
938
- **Example:**
939
-
940
- ```typescript example.ts
941
- // Inline types to simulate the PluginManager behavior
942
- type Plugin = {
943
- name: string
944
- onBeforeScan?: () => Promise<void>
945
- }
946
-
947
- // Simulated PluginManager class (self-contained, no external imports)
948
- class PluginManager {
949
- private plugins: Plugin[]
950
-
951
- constructor(plugins: Plugin[]) {
952
- this.plugins = plugins
953
- }
954
-
955
- private async runHook(hookName: keyof Plugin): Promise<void> {
956
- for (const plugin of this.plugins) {
957
- const hook = plugin[hookName]
958
- if (typeof hook === 'function') {
959
- console.log(`[PluginManager] Running "${hookName}" for plugin: ${plugin.name}`)
960
- await (hook as () => Promise<void>)()
961
- }
962
- }
963
- }
964
-
965
- async onInit(): Promise<void> {
966
- await this.runHook('onInit')
967
- }
968
-
969
- async onBeforeScan(): Promise<void> {
970
- await this.runHook('onBeforeScan')
971
- }
972
- }
973
-
974
- // Example plugins with onBeforeScan hooks
975
- const plugins: Plugin[] = [
976
- {
977
- name: 'CachePlugin',
978
- onBeforeScan: async () => {
979
- console.log(' → CachePlugin: Clearing stale cache entries...')
980
- // Simulate async cache-clearing work
981
- await new Promise((resolve) => setTimeout(resolve, 10))
982
- console.log(' → CachePlugin: Cache cleared.')
983
- },
984
- },
985
- {
986
- name: 'OutputPlugin',
987
- onBeforeScan: async () => {
988
- console.log(' → OutputPlugin: Ensuring output directory exists...')
989
- await new Promise((resolve) => setTimeout(resolve, 5))
990
- console.log(' → OutputPlugin: Output directory ready.')
991
- },
992
- },
993
- {
994
- name: 'LoggerPlugin',
995
- // No onBeforeScan hook — will be skipped automatically
996
- },
997
- ]
998
-
999
- async function main() {
1000
- const manager = new PluginManager(plugins)
1001
-
1002
- try {
1003
- console.log('Step 1: Initializing plugins...')
1004
- await manager.onInit()
1005
-
1006
- console.log('\nStep 2: Running pre-scan hooks...')
1007
- await manager.onBeforeScan()
1008
-
1009
- console.log('\n✓ All onBeforeScan hooks completed. Safe to begin scanning.')
1010
- // Output:
1011
- // Step 1: Initializing plugins...
1012
- // Step 2: Running pre-scan hooks...
1013
- // [PluginManager] Running "onBeforeScan" for plugin: CachePlugin
1014
- // → CachePlugin: Clearing stale cache entries...
1015
- // → CachePlugin: Cache cleared.
1016
- // [PluginManager] Running "onBeforeScan" for plugin: OutputPlugin
1017
- // → OutputPlugin: Ensuring output directory exists...
1018
- // → OutputPlugin: Output directory ready.
1019
- // ✓ All onBeforeScan hooks completed. Safe to begin scanning.
1020
- } catch (error) {
1021
- console.error('onBeforeScan failed:', error)
1022
- process.exit(1)
1023
- }
1024
- }
1025
-
1026
- main()
1027
- ```
1028
-
1029
- #### `onAfterScan`
1030
-
1031
- ```typescript
1032
- async onAfterScan<T>(elements: T[]): Promise<T[]>
1033
- ```
1034
-
1035
- Use this to run all registered plugins' `onAfterScan` hooks against a list of scanned elements, allowing plugins to filter, transform, or enrich the results before further processing.
1036
-
1037
- This is called automatically by `PluginManager` after the scan phase completes. Each plugin that implements `onAfterScan` gets a chance to modify the elements array in sequence. If no plugin modifies the elements, the original array is returned unchanged.
1038
-
1039
- #### Parameters
1040
-
1041
- | Name | Type | Required | Description |
1042
- |------|------|----------|-------------|
1043
- | `elements` | `T[]` | Yes | The array of scanned elements to pass through the plugin hook chain |
1044
-
1045
- #### Returns
1046
-
1047
- **`Promise<T[]>`** — Resolves to the (potentially modified) array of elements after all plugins have processed them. Falls back to the original `elements` array if no plugin returns a value.
1048
-
1049
- ## Behavior Notes
1050
-
1051
- - Plugins are executed in registration order
1052
- - If a plugin's `onAfterScan` returns `null`/`undefined`, the previous elements array is preserved
1053
- - The generic type `T` is inferred from the input array — no explicit type annotation needed in most cases
1054
-
1055
- **Example:**
1056
-
1057
- ```typescript example.ts
1058
- // Inline types to simulate PluginManager behavior
1059
- type Plugin = {
1060
- name: string
1061
- onAfterScan?: <T>(elements: T[]) => Promise<T[]> | T[]
1062
- }
1063
-
1064
- type ScannedFile = {
1065
- path: string
1066
- exported: boolean
1067
- tags: string[]
1068
- }
1069
-
1070
- // Simulated PluginManager with onAfterScan
1071
- class PluginManager {
1072
- private plugins: Plugin[] = []
1073
-
1074
- register(plugin: Plugin) {
1075
- this.plugins.push(plugin)
1076
- console.log(`Plugin registered: ${plugin.name}`)
1077
- }
1078
-
1079
- private async runHook<R>(hookName: string, arg?: R): Promise<R | undefined> {
1080
- let result: R | undefined = arg
1081
- for (const plugin of this.plugins) {
1082
- const hook = (plugin as Record<string, unknown>)[hookName]
1083
- if (typeof hook === 'function') {
1084
- const hookResult = await hook.call(plugin, result)
1085
- if (hookResult !== undefined && hookResult !== null) {
1086
- result = hookResult
1087
- }
1088
- }
1089
- }
1090
- return result
1091
- }
1092
-
1093
- async onAfterScan<T>(elements: T[]): Promise<T[]> {
1094
- return (await this.runHook<T[]>('onAfterScan', elements)) || elements
1095
- }
1096
- }
1097
-
1098
- // --- Example usage ---
1099
-
1100
- const manager = new PluginManager()
1101
-
1102
- // Plugin 1: Filter out unexported files
1103
- manager.register({
1104
- name: 'export-filter-plugin',
1105
- onAfterScan: async <T>(elements: T[]): Promise<T[]> => {
1106
- const files = elements as unknown as ScannedFile[]
1107
- const filtered = files.filter((f) => f.exported)
1108
- console.log(`[export-filter-plugin] Filtered ${elements.length} → ${filtered.length} elements`)
1109
- return filtered as unknown as T[]
1110
- }
1111
- })
1112
-
1113
- // Plugin 2: Enrich elements with additional tags
1114
- manager.register({
1115
- name: 'tag-enrichment-plugin',
1116
- onAfterScan: async <T>(elements: T[]): Promise<T[]> => {
1117
- const files = elements as unknown as ScannedFile[]
1118
- const enriched = files.map((f) => ({ ...f, tags: [...f.tags, 'scanned'] }))
1119
- console.log(`[tag-enrichment-plugin] Added "scanned" tag to ${enriched.length} elements`)
1120
- return enriched as unknown as T[]
1121
- }
1122
- })
1123
-
1124
- // Simulated scan results
1125
- const scannedFiles: ScannedFile[] = [
1126
- { path: 'src/index.ts', exported: true, tags: ['entry'] },
1127
- { path: 'src/internal.ts', exported: false, tags: [] },
1128
- { path: 'src/utils.ts', exported: true, tags: ['utility'] },
1129
- { path: 'src/helpers.ts', exported: false, tags: [] },
1130
- ]
1131
-
1132
- async function main() {
1133
- try {
1134
- console.log('\nRunning onAfterScan with', scannedFiles.length, 'elements...\n')
1135
-
1136
- const result = await manager.onAfterScan(scannedFiles)
1137
-
1138
- console.log('\nFinal elements after all plugins:')
1139
- result.forEach((f) => {
1140
- console.log(` ${f.path} | tags: [${f.tags.join(', ')}]`)
1141
- })
1142
-
1143
- // Expected output:
1144
- // Plugin registered: export-filter-plugin
1145
- // Plugin registered: tag-enrichment-plugin
1146
- //
1147
- // Running onAfterScan with 4 elements...
1148
- //
1149
- // [export-filter-plugin] Filtered 4 → 2 elements
1150
- // [tag-enrichment-plugin] Added "scanned" tag to 2 elements
1151
- //
1152
- // Final elements after all plugins:
1153
- // src/index.ts | tags: [entry, scanned]
1154
- // src/utils.ts | tags: [utility, scanned]
1155
- } catch (error) {
1156
- console.error('onAfterScan failed:', error)
1157
- }
1158
- }
1159
-
1160
- main()
1161
- ```
1162
-
1163
- #### `onBeforeGenerate`
1164
-
1165
- ```typescript
1166
- async onBeforeGenerate<T>(elements: T[]): Promise<T[]>
1167
- ```
1168
-
1169
- Use this to run all registered plugins' `onBeforeGenerate` hooks against a collection of elements before documentation generation begins. This is the last chance to filter, transform, enrich, or reorder elements before the generation pipeline processes them.
1170
-
1171
- If no plugin modifies the elements (or no plugins are registered), the original array is returned unchanged.
1172
-
1173
- ### Parameters
1174
-
1175
- | Name | Type | Required | Description |
1176
- |------|------|----------|-------------|
1177
- | `elements` | `T[]` | Yes | The array of elements (e.g. parsed source items, doc nodes) to pass through all registered plugin hooks before generation |
1178
-
1179
- #### Returns
1180
-
1181
- **`Promise<T[]>`** — Resolves to the transformed array after all `onBeforeGenerate` plugin hooks have run. Falls back to the original `elements` array if no hook returns a value.
1182
-
1183
- #### When to use
1184
- - **Filter out elements** you don't want documented (e.g. internal/private members)
1185
- - **Enrich elements** with extra metadata before templates render them
1186
- - **Reorder or group** elements before the generator processes them
1187
- - **Inject synthetic elements** that don't exist in source but should appear in docs
1188
-
1189
- **Example:**
1190
-
1191
- ```typescript example.ts
1192
- // Inline types — no external imports needed
1193
- type HookName = 'onBeforeGenerate' | 'onAfterGenerate' | 'onAfterScan'
1194
-
1195
- interface Plugin {
1196
- name: string
1197
- onBeforeGenerate?: <T>(elements: T[]) => Promise<T[]>
1198
- }
1199
-
1200
- interface DocElement {
1201
- name: string
1202
- kind: 'function' | 'class' | 'interface'
1203
- isInternal: boolean
1204
- description?: string
1205
- }
1206
-
1207
- // Self-contained PluginManager implementation
1208
- class PluginManager {
1209
- private plugins: Plugin[] = []
1210
-
1211
- register(plugin: Plugin) {
1212
- this.plugins.push(plugin)
1213
- console.log(`Plugin registered: ${plugin.name}`)
1214
- }
1215
-
1216
- private async runHook<T>(hookName: HookName, elements: T[]): Promise<T | null> {
1217
- let current: T = elements as unknown as T
1218
- for (const plugin of this.plugins) {
1219
- const hook = plugin[hookName] as ((el: T) => Promise<T>) | undefined
1220
- if (typeof hook === 'function') {
1221
- current = await hook.call(plugin, current)
1222
- }
1223
- }
1224
- return current ?? null
1225
- }
1226
-
1227
- async onBeforeGenerate<T>(elements: T[]): Promise<T[]> {
1228
- return (await this.runHook<T[]>('onBeforeGenerate', elements)) || elements
1229
- }
1230
- }
1231
-
1232
- // --- Example usage ---
1233
-
1234
- const manager = new PluginManager()
1235
-
1236
- // Plugin 1: Strip internal/private elements before generation
1237
- manager.register({
1238
- name: 'filter-internals',
1239
- onBeforeGenerate: async <T>(elements: T[]): Promise<T[]> => {
1240
- const filtered = (elements as unknown as DocElement[]).filter(
1241
- (el) => !el.isInternal
1242
- )
1243
- console.log(`[filter-internals] Removed ${elements.length - filtered.length} internal element(s)`)
1244
- return filtered as unknown as T[]
1245
- },
1246
- })
1247
-
1248
- // Plugin 2: Enrich remaining elements with default descriptions
1249
- manager.register({
1250
- name: 'enrich-descriptions',
1251
- onBeforeGenerate: async <T>(elements: T[]): Promise<T[]> => {
1252
- const enriched = (elements as unknown as DocElement[]).map((el) => ({
1253
- ...el,
1254
- description: el.description ?? `Auto-generated docs for ${el.kind} "${el.name}"`,
1255
- }))
1256
- console.log(`[enrich-descriptions] Enriched ${enriched.length} element(s)`)
1257
- return enriched as unknown as T[]
1258
- },
1259
- })
1260
-
1261
- // Sample parsed source elements
1262
- const sourceElements: DocElement[] = [
1263
- { name: 'UserService', kind: 'class', isInternal: false },
1264
- { name: '_InternalUtil', kind: 'class', isInternal: true },
1265
- { name: 'fetchUser', kind: 'function', isInternal: false },
1266
- { name: 'IUserProfile', kind: 'interface', isInternal: false, description: 'Represents a user profile' },
1267
- ]
1268
-
1269
- async function main() {
1270
- try {
1271
- console.log(`\nInput elements (${sourceElements.length}):`, sourceElements.map(e => e.name))
1272
-
1273
- const result = await manager.onBeforeGenerate(sourceElements)
1274
-
1275
- console.log(`\nOutput elements (${result.length}):`)
1276
- result.forEach((el) => {
1277
- console.log(` [${el.kind}] ${el.name} — "${el.description}"`)
1278
- })
1279
-
1280
- // Expected output:
1281
- // Input elements (4): [ 'UserService', '_InternalUtil', 'fetchUser', 'IUserProfile' ]
1282
- // [filter-internals] Removed 1 internal element(s)
1283
- // [enrich-descriptions] Enriched 3 element(s)
1284
- // Output elements (3):
1285
- // [class] UserService — "Auto-generated docs for class "UserService""
1286
- // [function] fetchUser — "Auto-generated docs for function "fetchUser""
1287
- // [interface] IUserProfile — "Represents a user profile"
1288
- } catch (error) {
1289
- console.error('onBeforeGenerate failed:', error)
1290
- }
1291
- }
1292
-
1293
- main()
1294
- ```
1295
-
1296
- #### `onAfterGenerate`
1297
-
1298
- ```typescript
1299
- async onAfterGenerate<T>(docs: T[]): Promise<T[]>
1300
- ```
1301
-
1302
- Use this to run all registered plugins' `onAfterGenerate` hooks against a set of generated documentation objects, allowing plugins to transform, filter, or enrich docs immediately after generation but before they are written to disk.
1303
-
1304
- This is the second lifecycle hook in the documentation pipeline:
1305
- 1. `onBeforeGenerate` → elements are prepared
1306
- 2. **`onAfterGenerate`** → generated docs are post-processed ← you are here
1307
- 3. `onBeforeWrite` → docs are finalized before writing
1308
-
1309
- If no plugin hook modifies the docs, the original array is returned unchanged.
1310
-
1311
- ### Parameters
1312
-
1313
- | Name | Type | Required | Description |
1314
- |------|------|----------|-------------|
1315
- | `docs` | `T[]` | ✅ | Array of generated documentation objects to pass through registered plugin hooks |
1316
-
1317
- #### Returns
1318
-
1319
- **`Promise<T[]>`** — Resolves to the transformed array of docs after all plugins have processed them. Falls back to the original `docs` array if no plugin hook returns a value.
1320
-
1321
- **Example:**
1322
-
1323
- ```typescript example.ts
1324
- // Inline types to simulate the PluginManager behavior
1325
- type Hook<T> = (data: T) => Promise<T> | T
1326
-
1327
- interface Plugin {
1328
- onAfterGenerate?: Hook<any[]>
1329
- }
1330
-
1331
- interface GeneratedDoc {
1332
- id: string
1333
- title: string
1334
- content: string
1335
- tags?: string[]
1336
- }
1337
-
1338
- // Simulated PluginManager with onAfterGenerate support
1339
- class PluginManager {
1340
- private plugins: Plugin[] = []
1341
-
1342
- register(plugin: Plugin) {
1343
- this.plugins.push(plugin)
1344
- }
1345
-
1346
- private async runHook<T>(hookName: keyof Plugin, data: T): Promise<T | undefined> {
1347
- let result: T | undefined = undefined
1348
- for (const plugin of this.plugins) {
1349
- const hook = plugin[hookName] as Hook<T> | undefined
1350
- if (typeof hook === 'function') {
1351
- result = await hook(result ?? data)
1352
- }
1353
- }
1354
- return result
1355
- }
1356
-
1357
- async onAfterGenerate<T>(docs: T[]): Promise<T[]> {
1358
- return (await this.runHook<T[]>('onAfterGenerate', docs)) || docs
1359
- }
1360
- }
1361
-
1362
- // --- Example usage ---
1363
-
1364
- const manager = new PluginManager()
1365
-
1366
- // Plugin 1: Adds auto-generated tags based on content keywords
1367
- manager.register({
1368
- onAfterGenerate: async (docs: GeneratedDoc[]) => {
1369
- return docs.map(doc => ({
1370
- ...doc,
1371
- tags: doc.content.toLowerCase().includes('async')
1372
- ? ['async', 'promise']
1373
- : ['sync'],
1374
- }))
1375
- },
1376
- })
1377
-
1378
- // Plugin 2: Filters out any docs marked as internal
1379
- manager.register({
1380
- onAfterGenerate: async (docs: GeneratedDoc[]) => {
1381
- return docs.filter(doc => !doc.title.startsWith('[internal]'))
1382
- },
1383
- })
1384
-
1385
- const rawDocs: GeneratedDoc[] = [
1386
- { id: 'doc-001', title: 'fetchUser', content: 'An async function that fetches a user by ID.' },
1387
- { id: 'doc-002', title: '[internal] authToken', content: 'Manages internal auth tokens.' },
1388
- { id: 'doc-003', title: 'formatDate', content: 'Formats a date string into locale format.' },
1389
- ]
1390
-
1391
- async function main() {
1392
- try {
1393
- const processedDocs = await manager.onAfterGenerate(rawDocs)
1394
-
1395
- console.log('Docs after onAfterGenerate:', JSON.stringify(processedDocs, null, 2))
1396
- // Expected output:
1397
- // [
1398
- // { id: 'doc-001', title: 'fetchUser', content: '...', tags: ['async', 'promise'] },
1399
- // { id: 'doc-003', title: 'formatDate', content: '...', tags: ['sync'] }
1400
- // ]
1401
- // Note: '[internal] authToken' was removed by Plugin 2
1402
- } catch (error) {
1403
- console.error('onAfterGenerate failed:', error)
1404
- }
1405
- }
1406
-
1407
- main()
1408
- ```
1409
-
1410
- #### `onBeforeWrite`
1411
-
1412
- ```typescript
1413
- async onBeforeWrite<T>(docs: T[]): Promise<T[]>
1414
- ```
1415
-
1416
- Use this to run all registered plugins' `onBeforeWrite` hooks against your generated docs array, allowing plugins to transform, filter, or enrich documents just before they are written to disk.
1417
-
1418
- This is the last opportunity in the pipeline to mutate documentation objects before persistence. Each plugin in the manager can modify the array; if no plugin handles the hook, the original array is returned unchanged.
1419
-
1420
- ### Parameters
1421
-
1422
- | Name | Type | Required | Description |
1423
- |------|------|----------|-------------|
1424
- | `docs` | `T[]` | Yes | Array of documentation objects to be passed through all registered plugin hooks before writing |
1425
-
1426
- #### Returns
1427
-
1428
- | Condition | Return Value |
1429
- |-----------|-------------|
1430
- | One or more plugins handle the hook | `Promise<T[]>` — the transformed array returned by the last plugin that handled it |
1431
- | No plugin handles the hook | `Promise<T[]>` — the original `docs` array, unchanged |
1432
-
1433
- **Example:**
1434
-
1435
- ```typescript example.ts
1436
- // Inline types to keep example self-contained
1437
- type Doc = {
1438
- id: string
1439
- title: string
1440
- content: string
1441
- slug?: string
1442
- writtenAt?: string
1443
- }
1444
-
1445
- type Hook = 'onBeforeWrite' | 'onAfterGenerate' | 'onAfterWrite'
1446
-
1447
- type Plugin = {
1448
- name: string
1449
- hooks?: Partial<Record<Hook, (data: unknown) => unknown>>
1450
- }
1451
-
1452
- // Minimal self-contained PluginManager implementation
1453
- class PluginManager {
1454
- private plugins: Plugin[] = []
1455
-
1456
- register(plugin: Plugin) {
1457
- this.plugins.push(plugin)
1458
- console.log(`Plugin registered: ${plugin.name}`)
1459
- }
1460
-
1461
- private async runHook<T>(hookName: Hook, data?: T): Promise<T | undefined> {
1462
- let result: T | undefined = data
1463
- for (const plugin of this.plugins) {
1464
- const hook = plugin.hooks?.[hookName]
1465
- if (hook) {
1466
- result = (await hook(result)) as T
1467
- console.log(` [${plugin.name}] ran hook: ${hookName}`)
1468
- }
1469
- }
1470
- return result
1471
- }
1472
-
1473
- async onBeforeWrite<T>(docs: T[]): Promise<T[]> {
1474
- return (await this.runHook<T[]>('onBeforeWrite', docs)) || docs
1475
- }
1476
- }
1477
-
1478
- // --- Example plugins ---
1479
-
1480
- // Plugin 1: Adds a slug derived from the title
1481
- const slugPlugin: Plugin = {
1482
- name: 'SlugPlugin',
1483
- hooks: {
1484
- onBeforeWrite: (data) => {
1485
- const docs = data as Doc[]
1486
- return docs.map((doc) => ({
1487
- ...doc,
1488
- slug: doc.title.toLowerCase().replace(/\s+/g, '-'),
1489
- }))
1490
- },
1491
- },
1492
- }
1493
-
1494
- // Plugin 2: Stamps each doc with the write timestamp
1495
- const timestampPlugin: Plugin = {
1496
- name: 'TimestampPlugin',
1497
- hooks: {
1498
- onBeforeWrite: (data) => {
1499
- const docs = data as Doc[]
1500
- return docs.map((doc) => ({
1501
- ...doc,
1502
- writtenAt: new Date().toISOString(),
1503
- }))
1504
- },
1505
- },
1506
- }
1507
-
1508
- // --- Run the pipeline ---
1509
- async function main() {
1510
- const manager = new PluginManager()
1511
- manager.register(slugPlugin)
1512
- manager.register(timestampPlugin)
1513
-
1514
- const rawDocs: Doc[] = [
1515
- { id: 'doc-001', title: 'Getting Started', content: 'Welcome to the docs.' },
1516
- { id: 'doc-002', title: 'API Reference', content: 'Full API details here.' },
1517
- { id: 'doc-003', title: 'Advanced Usage', content: 'Tips for power users.' },
1518
- ]
1519
-
1520
- console.log('\nRunning onBeforeWrite hooks...')
1521
-
1522
- try {
1523
- const finalDocs = await manager.onBeforeWrite<Doc>(rawDocs)
1524
-
1525
- console.log('\nDocs ready to write:')
1526
- finalDocs.forEach((doc) => {
1527
- console.log(` [${doc.id}] "${doc.title}" → slug: "${doc.slug}", writtenAt: ${doc.writtenAt}`)
1528
- })
1529
-
1530
- // Expected output (timestamps will vary):
1531
- // [doc-001] "Getting Started" → slug: "getting-started", writtenAt: 2024-05-01T12:00:00.000Z
1532
- // [doc-002] "API Reference" → slug: "api-reference", writtenAt: 2024-05-01T12:00:00.000Z
1533
- // [doc-003] "Advanced Usage" → slug: "advanced-usage", writtenAt: 2024-05-01T12:00:00.000Z
1534
- } catch (error) {
1535
- console.error('onBeforeWrite pipeline failed:', error)
1536
- }
1537
- }
1538
-
1539
- main()
1540
- ```
1541
-
1542
- #### `onAfterWrite`
1543
-
1544
- ```typescript
1545
- async onAfterWrite(): Promise<void>
1546
- ```
1547
-
1548
- Use this to trigger all registered plugin hooks after documentation has been written to disk — ideal for post-write tasks like cache invalidation, notifications, or cleanup.
1549
-
1550
- `onAfterWrite` runs through every loaded plugin's `onAfterWrite` hook in sequence. It resolves when all hooks complete and does not return a value. If no plugins define this hook, it resolves immediately.
1551
-
1552
- ### Parameters
1553
-
1554
- This method takes no parameters.
1555
-
1556
- #### Returns
1557
-
1558
- | Type | Description |
1559
- |------|-------------|
1560
- | `Promise<void>` | Resolves when all plugin `onAfterWrite` hooks have completed |
1561
-
1562
- #### When to Call
1563
-
1564
- Call this immediately after all documentation files have been written. It pairs with `onBeforeWrite` (pre-write transformation) to form a complete write lifecycle:
1565
-
1566
- 1. `onBeforeWrite(docs)` — transform/filter docs before writing
1567
- 2. *(write files to disk)*
1568
- 3. `onAfterWrite()` — notify plugins that writing is complete
1569
-
1570
- **Example:**
1571
-
1572
- ```typescript example.ts
1573
- // Inline types to keep example self-contained
1574
- type Hook = (...args: unknown[]) => Promise<unknown>
1575
-
1576
- interface Plugin {
1577
- name: string
1578
- hooks: Record<string, Hook>
1579
- }
1580
-
1581
- // Simulated PluginManager with onAfterWrite
1582
- class PluginManager {
1583
- private plugins: Plugin[] = []
1584
-
1585
- register(plugin: Plugin): void {
1586
- this.plugins.push(plugin)
1587
- console.log(`Plugin registered: ${plugin.name}`)
1588
- }
1589
-
1590
- private async runHook(hookName: string, ...args: unknown[]): Promise<unknown> {
1591
- let result: unknown
1592
- for (const plugin of this.plugins) {
1593
- if (typeof plugin.hooks[hookName] === 'function') {
1594
- result = await plugin.hooks[hookName](...args)
1595
- }
1596
- }
1597
- return result
1598
- }
1599
-
1600
- async onBeforeWrite<T>(docs: T[]): Promise<T[]> {
1601
- return (await this.runHook('onBeforeWrite', docs) as T[]) || docs
1602
- }
1603
-
1604
- async onAfterWrite(): Promise<void> {
1605
- await this.runHook('onAfterWrite')
1606
- }
1607
- }
1608
-
1609
- // Example plugins that use the onAfterWrite hook
1610
- const cacheInvalidationPlugin: Plugin = {
1611
- name: 'cache-invalidation',
1612
- hooks: {
1613
- onAfterWrite: async () => {
1614
- console.log('[cache-invalidation] Clearing documentation cache...')
1615
- // Simulate async cache clear
1616
- await new Promise(resolve => setTimeout(resolve, 50))
1617
- console.log('[cache-invalidation] Cache cleared successfully.')
1618
- }
1619
- }
1620
- }
1621
-
1622
- const notificationPlugin: Plugin = {
1623
- name: 'slack-notifier',
1624
- hooks: {
1625
- onAfterWrite: async () => {
1626
- const webhookUrl = process.env.SLACK_WEBHOOK_URL || 'https://hooks.slack.com/your-webhook'
1627
- console.log(`[slack-notifier] Sending notification to: ${webhookUrl}`)
1628
- // Simulate async notification
1629
- await new Promise(resolve => setTimeout(resolve, 30))
1630
- console.log('[slack-notifier] Notification sent: docs updated.')
1631
- }
1632
- }
1633
- }
1634
-
1635
- async function main() {
1636
- const manager = new PluginManager()
1637
-
1638
- manager.register(cacheInvalidationPlugin)
1639
- manager.register(notificationPlugin)
1640
-
1641
- // Simulate the full write lifecycle
1642
- const docs = [
1643
- { id: 'doc-001', title: 'Getting Started', content: 'Welcome...' },
1644
- { id: 'doc-002', title: 'API Reference', content: 'Endpoints...' }
1645
- ]
1646
-
1647
- try {
1648
- console.log('\n--- Step 1: Pre-write hook ---')
1649
- const processedDocs = await manager.onBeforeWrite(docs)
1650
- console.log(`Processed ${processedDocs.length} docs for writing.`)
1651
-
1652
- console.log('\n--- Step 2: Writing files to disk (simulated) ---')
1653
- await new Promise(resolve => setTimeout(resolve, 20))
1654
- console.log('Files written successfully.')
1655
-
1656
- console.log('\n--- Step 3: Post-write hook ---')
1657
- await manager.onAfterWrite()
1658
-
1659
- console.log('\n✅ Write lifecycle complete.')
1660
- // Expected output:
1661
- // [cache-invalidation] Clearing documentation cache...
1662
- // [cache-invalidation] Cache cleared successfully.
1663
- // [slack-notifier] Sending notification to: https://hooks.slack.com/your-webhook
1664
- // [slack-notifier] Notification sent: docs updated.
1665
- } catch (error) {
1666
- console.error('Write lifecycle failed:', error)
1667
- process.exit(1)
1668
- }
1669
- }
1670
-
1671
- main()
1672
- ```
1673
-
1674
- #### `transformContent`
1675
-
1676
- ```typescript
1677
- async transformContent(content: string, filePath: string): Promise<string>
1678
- ```
1679
-
1680
- Use this to pipe file content through a chain of registered plugins, each of which can modify the content before it's written to disk.
1681
-
1682
- `transformContent` iterates over all registered plugins in order, passing the current content string to each plugin's `transformContent` method. The output of each plugin becomes the input for the next, making it a sequential transformation pipeline. If a plugin doesn't implement `transformContent`, it is skipped.
1683
-
1684
- #### Parameters
1685
-
1686
- | Name | Type | Required | Description |
1687
- |------|------|----------|-------------|
1688
- | `content` | `string` | ✅ | The raw file content to be transformed |
1689
- | `filePath` | `string` | ✅ | The path of the file being processed — plugins may use this to apply transformations conditionally based on file type or location |
1690
-
1691
- #### Returns
1692
-
1693
- | Condition | Return Value |
1694
- |-----------|-------------|
1695
- | One or more plugins transform the content | `Promise<string>` — the fully transformed content after all plugins have run |
1696
- | No plugins implement `transformContent` | `Promise<string>` — the original `content` string, unchanged |
1697
-
1698
- **Example:**
1699
-
1700
- ```typescript example.ts
1701
- // Inline plugin interface — no external imports needed
1702
- interface Plugin {
1703
- name: string;
1704
- transformContent?: (content: string, filePath: string) => Promise<string>;
1705
- }
1706
-
1707
- // Inline PluginManager implementation
1708
- class PluginManager {
1709
- private plugins: Plugin[] = [];
1710
-
1711
- register(plugin: Plugin): void {
1712
- this.plugins.push(plugin);
1713
- console.log(`Registered plugin: ${plugin.name}`);
1714
- }
1715
-
1716
- async transformContent(content: string, filePath: string): Promise<string> {
1717
- let result = content;
1718
-
1719
- for (const plugin of this.plugins) {
1720
- if (typeof plugin.transformContent === 'function') {
1721
- try {
1722
- result = await plugin.transformContent(result, filePath);
1723
- } catch (error) {
1724
- console.error(`Plugin "${plugin.name}" failed during transformContent:`, error);
1725
- }
1726
- }
1727
- }
1728
-
1729
- return result;
1730
- }
1731
- }
1732
-
1733
- // --- Example plugins ---
1734
-
1735
- // Plugin 1: Injects a timestamp header into markdown files
1736
- const timestampPlugin: Plugin = {
1737
- name: 'timestamp-injector',
1738
- async transformContent(content, filePath) {
1739
- if (filePath.endsWith('.md')) {
1740
- return `<!-- Generated: ${new Date().toISOString()} -->\n${content}`;
1741
- }
1742
- return content;
1743
- },
1744
- };
1745
-
1746
- // Plugin 2: Replaces placeholder tokens with environment values
1747
- const envReplacerPlugin: Plugin = {
1748
- name: 'env-replacer',
1749
- async transformContent(content, _filePath) {
1750
- const apiBase = process.env.API_BASE_URL || 'https://api.example.com';
1751
- return content.replace(/\{\{API_BASE_URL\}\}/g, apiBase);
1752
- },
1753
- };
1754
-
1755
- // Plugin 3: No transformContent — should be silently skipped
1756
- const noOpPlugin: Plugin = {
1757
- name: 'lifecycle-only-plugin',
1758
- // intentionally omits transformContent
1759
- };
1760
-
1761
- // --- Run the pipeline ---
1762
- async function main() {
1763
- try {
1764
- const manager = new PluginManager();
1765
- manager.register(timestampPlugin);
1766
- manager.register(envReplacerPlugin);
1767
- manager.register(noOpPlugin);
1768
-
1769
- const rawContent = `# API Reference\n\nBase URL: {{API_BASE_URL}}\n\nWelcome to the docs.`;
1770
- const filePath = 'docs/api-reference.md';
1771
-
1772
- console.log('\n--- Input ---');
1773
- console.log(rawContent);
1774
-
1775
- const transformed = await manager.transformContent(rawContent, filePath);
1776
-
1777
- console.log('\n--- Transformed Output ---');
1778
- console.log(transformed);
1779
- // Expected output:
1780
- // <!-- Generated: 2024-01-15T10:30:00.000Z -->
1781
- // # API Reference
1782
- //
1783
- // Base URL: https://api.example.com
1784
- //
1785
- // Welcome to the docs.
1786
- } catch (error) {
1787
- console.error('Transformation pipeline failed:', error);
1788
- }
1789
- }
1790
-
1791
- main();
1792
- ```
1793
-