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.
- package/dist/auth/index.js +3 -3
- package/dist/cli.js +1 -1
- package/dist/commands/cron.js +0 -4
- package/dist/commands/generate/index.d.ts +3 -0
- package/dist/commands/generate/index.js +393 -0
- package/dist/commands/generate/scan.d.ts +41 -0
- package/dist/commands/generate/scan.js +256 -0
- package/dist/commands/generate/verify.d.ts +14 -0
- package/dist/commands/generate/verify.js +122 -0
- package/dist/commands/generate/write.d.ts +25 -0
- package/dist/commands/generate/write.js +120 -0
- package/dist/commands/import.js +4 -1
- package/dist/commands/llms-txt.js +6 -4
- package/dist/config/loader.d.ts +0 -1
- package/dist/config/loader.js +1 -1
- package/dist/generator/agents-md.d.ts +25 -0
- package/dist/generator/agents-md.js +122 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/mdx-serializer.d.ts +11 -0
- package/dist/generator/mdx-serializer.js +135 -0
- package/dist/generator/organizer.d.ts +1 -16
- package/dist/generator/organizer.js +0 -38
- package/dist/generator/writer.js +5 -4
- package/dist/llm/proxy-client.d.ts +32 -0
- package/dist/llm/proxy-client.js +103 -0
- package/dist/scanner/csharp.d.ts +0 -4
- package/dist/scanner/csharp.js +9 -49
- package/dist/scanner/go.d.ts +0 -3
- package/dist/scanner/go.js +8 -35
- package/dist/scanner/java.d.ts +0 -4
- package/dist/scanner/java.js +9 -49
- package/dist/scanner/kotlin.d.ts +0 -3
- package/dist/scanner/kotlin.js +6 -33
- package/dist/scanner/php.d.ts +0 -10
- package/dist/scanner/php.js +11 -55
- package/dist/scanner/ruby.d.ts +0 -3
- package/dist/scanner/ruby.js +8 -38
- package/dist/scanner/rust.d.ts +0 -3
- package/dist/scanner/rust.js +10 -37
- package/dist/scanner/swift.d.ts +0 -3
- package/dist/scanner/swift.js +8 -35
- package/dist/scanner/utils.d.ts +41 -0
- package/dist/scanner/utils.js +97 -0
- package/dist/template/docs.json +5 -2
- package/dist/template/next.config.mjs +31 -0
- package/dist/template/package.json +5 -3
- package/dist/template/src/app/layout.tsx +13 -13
- package/dist/template/src/app/llms-full.md/route.ts +29 -0
- package/dist/template/src/app/llms.txt/route.ts +29 -0
- package/dist/template/src/app/md/[...slug]/route.ts +174 -0
- package/dist/template/src/app/reference/route.ts +22 -18
- package/dist/template/src/app/sitemap.ts +1 -1
- package/dist/template/src/components/ai-chat-impl.tsx +206 -0
- package/dist/template/src/components/ai-chat.tsx +20 -193
- package/dist/template/src/components/mdx/index.tsx +27 -4
- package/dist/template/src/lib/fonts.ts +135 -0
- package/dist/template/src/middleware.ts +101 -0
- package/dist/template/src/styles/globals.css +28 -20
- package/dist/utils/files.d.ts +0 -8
- package/dist/utils/files.js +0 -33
- package/package.json +1 -1
- package/dist/autofix/autofix.test.d.ts +0 -1
- package/dist/autofix/autofix.test.js +0 -487
- package/dist/commands/generate.d.ts +0 -9
- package/dist/commands/generate.js +0 -739
- package/dist/generator/generator.test.d.ts +0 -1
- package/dist/generator/generator.test.js +0 -259
- package/dist/generator/writer.test.d.ts +0 -1
- package/dist/generator/writer.test.js +0 -411
- package/dist/llm/llm.manual-test.d.ts +0 -1
- package/dist/llm/llm.manual-test.js +0 -112
- package/dist/llm/llm.mock-test.d.ts +0 -4
- package/dist/llm/llm.mock-test.js +0 -79
- package/dist/plugins/index.d.ts +0 -47
- package/dist/plugins/index.js +0 -181
- package/dist/scanner/content-type.test.d.ts +0 -1
- package/dist/scanner/content-type.test.js +0 -231
- package/dist/scanner/integration.test.d.ts +0 -4
- package/dist/scanner/integration.test.js +0 -180
- package/dist/scanner/scanner.test.d.ts +0 -1
- package/dist/scanner/scanner.test.js +0 -210
- package/dist/scanner/typescript.manual-test.d.ts +0 -1
- package/dist/scanner/typescript.manual-test.js +0 -112
- package/dist/template/src/app/docs/auth/page.mdx +0 -589
- package/dist/template/src/app/docs/autofix/page.mdx +0 -624
- package/dist/template/src/app/docs/cli/page.mdx +0 -217
- package/dist/template/src/app/docs/config/page.mdx +0 -428
- package/dist/template/src/app/docs/configuration/page.mdx +0 -86
- package/dist/template/src/app/docs/deployment/page.mdx +0 -112
- package/dist/template/src/app/docs/generator/generator.md +0 -504
- package/dist/template/src/app/docs/generator/organizer.md +0 -779
- package/dist/template/src/app/docs/generator/page.mdx +0 -613
- package/dist/template/src/app/docs/github/page.mdx +0 -502
- package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
- package/dist/template/src/app/docs/llm/index.md +0 -471
- package/dist/template/src/app/docs/llm/page.mdx +0 -428
- package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
- package/dist/template/src/app/docs/pro/page.mdx +0 -121
- package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
- package/dist/template/src/app/docs/scanner/content-type.md +0 -599
- package/dist/template/src/app/docs/scanner/index.md +0 -212
- package/dist/template/src/app/docs/scanner/page.mdx +0 -307
- package/dist/template/src/app/docs/scanner/python.md +0 -469
- package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
- package/dist/template/src/app/docs/scanner/rust.md +0 -325
- package/dist/template/src/app/docs/scanner/typescript.md +0 -201
- package/dist/template/src/app/icon.tsx +0 -29
- package/dist/utils/validation.d.ts +0 -1
- 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
|
-
|