skrypt-ai 0.5.0 → 0.6.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 +8 -1
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +0 -21
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +88 -6
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +61 -43
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -102
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.js +86 -80
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +24 -4
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +16 -2
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +84 -6
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +3 -2
|
@@ -0,0 +1,4544 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Core API"
|
|
3
|
+
description: "Core functionality and main exports"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<CardGroup cols={2}>
|
|
7
|
+
<Card title="PluginManager" icon="cube" href="#pluginmanager">
|
|
8
|
+
Manages documentation build plugins
|
|
9
|
+
</Card>
|
|
10
|
+
<Card title="autoFixBatch" icon="function" href="#autofixbatch">
|
|
11
|
+
Batch-fixes broken code examples
|
|
12
|
+
</Card>
|
|
13
|
+
<Card title="autoFixExample" icon="function" href="#autofixexample">
|
|
14
|
+
Repairs broken code examples
|
|
15
|
+
</Card>
|
|
16
|
+
<Card title="checkPlan" icon="function" href="#checkplan">
|
|
17
|
+
Verifies API key and plan
|
|
18
|
+
</Card>
|
|
19
|
+
<Card title="clearAuth" icon="function" href="#clearauth">
|
|
20
|
+
Wipes all stored credentials
|
|
21
|
+
</Card>
|
|
22
|
+
<Card title="createLLMClient" icon="function" href="#createllmclient">
|
|
23
|
+
Initializes typed LLM client
|
|
24
|
+
</Card>
|
|
25
|
+
<Card title="createPythonValidator" icon="function" href="#createpythonvalidator">
|
|
26
|
+
Validates Python code syntax
|
|
27
|
+
</Card>
|
|
28
|
+
<Card title="createTypeScriptValidator" icon="function" href="#createtypescriptvalidator">
|
|
29
|
+
Validates TypeScript code strings
|
|
30
|
+
</Card>
|
|
31
|
+
<Card title="definePlugin" icon="function" href="#defineplugin">
|
|
32
|
+
Defines typed plugin config
|
|
33
|
+
</Card>
|
|
34
|
+
<Card title="fixCodeSample" icon="function" href="#fixcodesample">
|
|
35
|
+
Auto-repairs broken code samples
|
|
36
|
+
</Card>
|
|
37
|
+
<Card title="generateDocumentation" icon="function" href="#generatedocumentation">
|
|
38
|
+
Generates docs via LLM client
|
|
39
|
+
</Card>
|
|
40
|
+
<Card title="getAuthConfig" icon="function" href="#getauthconfig">
|
|
41
|
+
Retrieves auth config synchronously
|
|
42
|
+
</Card>
|
|
43
|
+
<Card title="getAuthConfigAsync" icon="function" href="#getauthconfigasync">
|
|
44
|
+
Retrieves auth config asynchronously
|
|
45
|
+
</Card>
|
|
46
|
+
<Card title="getKeyStorageMethod" icon="function" href="#getkeystoragemethod">
|
|
47
|
+
Detects API key storage location
|
|
48
|
+
</Card>
|
|
49
|
+
<Card title="requirePro" icon="function" href="#requirepro">
|
|
50
|
+
Gates commands behind Pro plan
|
|
51
|
+
</Card>
|
|
52
|
+
<Card title="runImport" icon="function" href="#runimport">
|
|
53
|
+
Imports docs by detected format
|
|
54
|
+
</Card>
|
|
55
|
+
<Card title="saveAuthConfig" icon="function" href="#saveauthconfig">
|
|
56
|
+
Persists auth config to disk
|
|
57
|
+
</Card>
|
|
58
|
+
<Card title="scanDirectory" icon="function" href="#scandirectory">
|
|
59
|
+
Scans directory for API elements
|
|
60
|
+
</Card>
|
|
61
|
+
<Card title="scanFile" icon="function" href="#scanfile">
|
|
62
|
+
Extracts metadata from source file
|
|
63
|
+
</Card>
|
|
64
|
+
<Card title="constructor" icon="code" href="#constructor">
|
|
65
|
+
Initializes plugin manager instance
|
|
66
|
+
</Card>
|
|
67
|
+
<Card title="loadPlugins" icon="code" href="#loadplugins">
|
|
68
|
+
Loads plugins from config file
|
|
69
|
+
</Card>
|
|
70
|
+
<Card title="onAfterGenerate" icon="code" href="#onaftergenerate">
|
|
71
|
+
Runs post-generation plugin hooks
|
|
72
|
+
</Card>
|
|
73
|
+
<Card title="onAfterScan" icon="code" href="#onafterscan">
|
|
74
|
+
Runs post-scan plugin hooks
|
|
75
|
+
</Card>
|
|
76
|
+
<Card title="onAfterWrite" icon="code" href="#onafterwrite">
|
|
77
|
+
Runs post-write plugin hooks
|
|
78
|
+
</Card>
|
|
79
|
+
<Card title="onBeforeGenerate" icon="code" href="#onbeforegenerate">
|
|
80
|
+
Runs pre-generation plugin hooks
|
|
81
|
+
</Card>
|
|
82
|
+
<Card title="onBeforeScan" icon="code" href="#onbeforescan">
|
|
83
|
+
Runs pre-scan plugin hooks
|
|
84
|
+
</Card>
|
|
85
|
+
<Card title="onBeforeWrite" icon="code" href="#onbeforewrite">
|
|
86
|
+
Runs pre-write plugin hooks
|
|
87
|
+
</Card>
|
|
88
|
+
<Card title="onInit" icon="code" href="#oninit">
|
|
89
|
+
Triggers plugin initialization hook
|
|
90
|
+
</Card>
|
|
91
|
+
<Card title="register" icon="code" href="#register">
|
|
92
|
+
Registers plugin with manager
|
|
93
|
+
</Card>
|
|
94
|
+
<Card title="runHook" icon="code" href="#runhook">
|
|
95
|
+
Executes named hook across plugins
|
|
96
|
+
</Card>
|
|
97
|
+
<Card title="transformContent" icon="code" href="#transformcontent">
|
|
98
|
+
Pipes content through plugin chain
|
|
99
|
+
</Card>
|
|
100
|
+
</CardGroup>
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
## `PluginManager`
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
class PluginManager
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use this to load, manage, and execute documentation plugins that transform source files into output during the autodocs build process.
|
|
110
|
+
|
|
111
|
+
`PluginManager` orchestrates the full plugin lifecycle — registering plugins, running them in sequence, and providing each plugin with shared context (paths, config, and a logger).
|
|
112
|
+
|
|
113
|
+
## Constructor Parameters
|
|
114
|
+
|
|
115
|
+
| Name | Type | Required | Description |
|
|
116
|
+
|------|------|----------|-------------|
|
|
117
|
+
| `sourcePath` | `string` | Yes | Absolute or relative path to the source files directory |
|
|
118
|
+
| `outputPath` | `string` | Yes | Absolute or relative path where generated docs will be written |
|
|
119
|
+
| `config` | `Record<string, unknown>` | No | Arbitrary config values passed to every plugin via context (defaults to `{}`) |
|
|
120
|
+
|
|
121
|
+
## Plugin Context
|
|
122
|
+
|
|
123
|
+
Every registered plugin receives a shared context object:
|
|
124
|
+
|
|
125
|
+
| Field | Type | Description |
|
|
126
|
+
|-------|------|-------------|
|
|
127
|
+
| `sourcePath` | `string` | The source path passed to the constructor |
|
|
128
|
+
| `outputPath` | `string` | The output path passed to the constructor |
|
|
129
|
+
| `config` | `Record<string, unknown>` | The config object passed to the constructor |
|
|
130
|
+
| `logger.info` | `(msg: string) => void` | Logs an info message prefixed with `[plugin]` |
|
|
131
|
+
| `logger.warn` | `(msg: string) => void` | Logs a warning prefixed with `[plugin]` |
|
|
132
|
+
| `logger.error` | `(msg: string) => void` | Logs an error prefixed with `[plugin]` |
|
|
133
|
+
|
|
134
|
+
## Key Behaviors
|
|
135
|
+
|
|
136
|
+
- Plugins are stored and executed in registration order
|
|
137
|
+
- Each plugin receives the same shared `PluginContext` instance
|
|
138
|
+
- Logger output is prefixed with `[plugin]` for easy filtering in build logs
|
|
139
|
+
- Config defaults to an empty object if not provided, so plugins should handle missing config keys gracefully
|
|
140
|
+
|
|
141
|
+
### Example
|
|
142
|
+
|
|
143
|
+
```typescript example.ts
|
|
144
|
+
// --- Inline types (no external imports needed) ---
|
|
145
|
+
|
|
146
|
+
type PluginContext = {
|
|
147
|
+
sourcePath: string
|
|
148
|
+
outputPath: string
|
|
149
|
+
config: Record<string, unknown>
|
|
150
|
+
logger: {
|
|
151
|
+
info: (msg: string) => void
|
|
152
|
+
warn: (msg: string) => void
|
|
153
|
+
error: (msg: string) => void
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
type SkryptPlugin = {
|
|
158
|
+
name: string
|
|
159
|
+
run: (context: PluginContext) => Promise<void>
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- Self-contained PluginManager implementation ---
|
|
163
|
+
|
|
164
|
+
class PluginManager {
|
|
165
|
+
private plugins: SkryptPlugin[] = []
|
|
166
|
+
private context: PluginContext
|
|
167
|
+
|
|
168
|
+
constructor(
|
|
169
|
+
sourcePath: string,
|
|
170
|
+
outputPath: string,
|
|
171
|
+
config: Record<string, unknown> = {}
|
|
172
|
+
) {
|
|
173
|
+
this.context = {
|
|
174
|
+
sourcePath,
|
|
175
|
+
outputPath,
|
|
176
|
+
config,
|
|
177
|
+
logger: {
|
|
178
|
+
info: (msg) => console.log(`[plugin] INFO: ${msg}`),
|
|
179
|
+
warn: (msg) => console.log(`[plugin] WARN: ${msg}`),
|
|
180
|
+
error: (msg) => console.log(`[plugin] ERROR: ${msg}`),
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
register(plugin: SkryptPlugin): void {
|
|
186
|
+
this.plugins.push(plugin)
|
|
187
|
+
this.context.logger.info(`Registered plugin: ${plugin.name}`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async runAll(): Promise<void> {
|
|
191
|
+
for (const plugin of this.plugins) {
|
|
192
|
+
this.context.logger.info(`Running plugin: ${plugin.name}`)
|
|
193
|
+
await plugin.run(this.context)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- Example plugins ---
|
|
199
|
+
|
|
200
|
+
const markdownPlugin: SkryptPlugin = {
|
|
201
|
+
name: 'markdown-renderer',
|
|
202
|
+
run: async (ctx) => {
|
|
203
|
+
const outputFormat = ctx.config.outputFormat ?? 'markdown'
|
|
204
|
+
ctx.logger.info(`Rendering docs from "${ctx.sourcePath}" as ${outputFormat}`)
|
|
205
|
+
// Simulate async file processing
|
|
206
|
+
await new Promise((res) => setTimeout(res, 10))
|
|
207
|
+
ctx.logger.info(`Wrote output to "${ctx.outputPath}"`)
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const lintPlugin: SkryptPlugin = {
|
|
212
|
+
name: 'doc-linter',
|
|
213
|
+
run: async (ctx) => {
|
|
214
|
+
const strict = ctx.config.strict ?? false
|
|
215
|
+
ctx.logger.info(`Linting docs (strict=${strict})`)
|
|
216
|
+
if (!strict) {
|
|
217
|
+
ctx.logger.warn('Strict mode is off — some issues may be skipped')
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// --- Main usage ---
|
|
223
|
+
|
|
224
|
+
async function main() {
|
|
225
|
+
try {
|
|
226
|
+
const manager = new PluginManager(
|
|
227
|
+
'./src',
|
|
228
|
+
'./docs/output',
|
|
229
|
+
{
|
|
230
|
+
outputFormat: 'html',
|
|
231
|
+
strict: true,
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
manager.register(markdownPlugin)
|
|
236
|
+
manager.register(lintPlugin)
|
|
237
|
+
|
|
238
|
+
await manager.runAll()
|
|
239
|
+
|
|
240
|
+
console.log('\n✅ All plugins completed successfully.')
|
|
241
|
+
// Expected output:
|
|
242
|
+
// [plugin] INFO: Registered plugin: markdown-renderer
|
|
243
|
+
// [plugin] INFO: Registered plugin: doc-linter
|
|
244
|
+
// [plugin] INFO: Running plugin: markdown-renderer
|
|
245
|
+
// [plugin] INFO: Rendering docs from "./src" as html
|
|
246
|
+
// [plugin] INFO: Wrote output to "./docs/output"
|
|
247
|
+
// [plugin] INFO: Running plugin: doc-linter
|
|
248
|
+
// [plugin] INFO: Linting docs (strict=true)
|
|
249
|
+
// ✅ All plugins completed successfully.
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error('Plugin pipeline failed:', error)
|
|
252
|
+
process.exit(1)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
main()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Related
|
|
260
|
+
|
|
261
|
+
<CardGroup cols={3}>
|
|
262
|
+
<Card title="constructor" icon="link" href="#constructor">
|
|
263
|
+
Uses
|
|
264
|
+
</Card>
|
|
265
|
+
<Card title="constructor" icon="link" href="#constructor">
|
|
266
|
+
Used by
|
|
267
|
+
</Card>
|
|
268
|
+
</CardGroup>
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
## `autoFixBatch`
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
async function autoFixBatch(examples: CodeExample[], client: LLMClient, options: AutoFixOptions = {}): Promise<Map<number, FixResult>>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Use this to automatically fix multiple broken code examples in a single batch operation, getting back a map of results indexed by position.
|
|
280
|
+
|
|
281
|
+
This is the batch version of `autoFix` — ideal when you have a collection of code snippets that need validation and repair, such as processing documentation examples, test fixtures, or user-submitted code samples all at once.
|
|
282
|
+
|
|
283
|
+
## Parameters
|
|
284
|
+
|
|
285
|
+
| Name | Type | Required | Description |
|
|
286
|
+
|------|------|----------|-------------|
|
|
287
|
+
| `examples` | `CodeExample[]` | ✅ | Array of code examples to fix. Each example contains the code string and metadata. |
|
|
288
|
+
| `client` | `LLMClient` | ✅ | LLM client instance used to generate fixes for broken examples. |
|
|
289
|
+
| `options` | `AutoFixOptions` | ❌ | Configuration options such as max retry attempts, timeout, and fix strategy. Defaults to `{}`. |
|
|
290
|
+
|
|
291
|
+
## Returns
|
|
292
|
+
|
|
293
|
+
Returns `Promise<Map<number, FixResult>>` — a Map where:
|
|
294
|
+
- **Key**: the index of the example in the input array (0-based)
|
|
295
|
+
- **Value**: a `FixResult` object containing:
|
|
296
|
+
- `success: boolean` — whether the fix was applied successfully
|
|
297
|
+
- `fixedCode?: string` — the repaired code (present when `success` is `true`)
|
|
298
|
+
- `error?: string` — description of what went wrong (present when `success` is `false`)
|
|
299
|
+
- `attempts?: number` — how many fix attempts were made
|
|
300
|
+
|
|
301
|
+
Examples that could not be fixed will still appear in the map with `success: false`, so you can always check all inputs by iterating the original array indices.
|
|
302
|
+
|
|
303
|
+
### Example
|
|
304
|
+
|
|
305
|
+
```typescript example.ts
|
|
306
|
+
// ─── Inline types (do NOT import from autodocs) ───────────────────────────────
|
|
307
|
+
|
|
308
|
+
type CodeExample = {
|
|
309
|
+
code: string
|
|
310
|
+
language?: string
|
|
311
|
+
filename?: string
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
type FixResult = {
|
|
315
|
+
success: boolean
|
|
316
|
+
fixedCode?: string
|
|
317
|
+
error?: string
|
|
318
|
+
attempts?: number
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
type AutoFixOptions = {
|
|
322
|
+
maxAttempts?: number
|
|
323
|
+
timeoutMs?: number
|
|
324
|
+
verbose?: boolean
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
type LLMClient = {
|
|
328
|
+
complete: (prompt: string) => Promise<string>
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ─── Simulated autoFixBatch implementation ────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
async function autoFixBatch(
|
|
334
|
+
examples: CodeExample[],
|
|
335
|
+
client: LLMClient,
|
|
336
|
+
options: AutoFixOptions = {}
|
|
337
|
+
): Promise<Map<number, FixResult>> {
|
|
338
|
+
const { maxAttempts = 3, verbose = false } = options
|
|
339
|
+
const results = new Map<number, FixResult>()
|
|
340
|
+
|
|
341
|
+
for (let i = 0; i < examples.length; i++) {
|
|
342
|
+
const example = examples[i]
|
|
343
|
+
|
|
344
|
+
// Simulate a basic syntax check (real impl would use TypeScript compiler)
|
|
345
|
+
const hasSyntaxError = example.code.includes('???') || example.code.includes('BROKEN')
|
|
346
|
+
|
|
347
|
+
if (!hasSyntaxError) {
|
|
348
|
+
results.set(i, {
|
|
349
|
+
success: true,
|
|
350
|
+
fixedCode: example.code,
|
|
351
|
+
attempts: 1,
|
|
352
|
+
})
|
|
353
|
+
if (verbose) console.log(`[${i}] ✅ No fix needed`)
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
// Ask the LLM to fix the broken code
|
|
359
|
+
const prompt = `Fix this broken ${example.language ?? 'TypeScript'} code:\n\n${example.code}`
|
|
360
|
+
const fixedCode = await client.complete(prompt)
|
|
361
|
+
|
|
362
|
+
results.set(i, {
|
|
363
|
+
success: true,
|
|
364
|
+
fixedCode,
|
|
365
|
+
attempts: maxAttempts,
|
|
366
|
+
})
|
|
367
|
+
if (verbose) console.log(`[${i}] 🔧 Fixed after ${maxAttempts} attempt(s)`)
|
|
368
|
+
} catch (err) {
|
|
369
|
+
results.set(i, {
|
|
370
|
+
success: false,
|
|
371
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
372
|
+
attempts: maxAttempts,
|
|
373
|
+
})
|
|
374
|
+
if (verbose) console.log(`[${i}] ❌ Could not fix`)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return results
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ─── Mock LLM client ──────────────────────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
function createMockLLMClient(apiKey: string): LLMClient {
|
|
384
|
+
return {
|
|
385
|
+
complete: async (prompt: string): Promise<string> => {
|
|
386
|
+
// Simulate network latency
|
|
387
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
388
|
+
|
|
389
|
+
// Simulate the LLM returning a corrected snippet
|
|
390
|
+
if (prompt.includes('BROKEN')) {
|
|
391
|
+
return `const greet = (name: string): string => \`Hello, \${name}!\``
|
|
392
|
+
}
|
|
393
|
+
throw new Error('LLM could not determine a fix')
|
|
394
|
+
},
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ─── Main usage ───────────────────────────────────────────────────────────────
|
|
399
|
+
|
|
400
|
+
const examples: CodeExample[] = [
|
|
401
|
+
{
|
|
402
|
+
// Valid example — should pass through unchanged
|
|
403
|
+
code: `const add = (a: number, b: number): number => a + b`,
|
|
404
|
+
language: 'typescript',
|
|
405
|
+
filename: 'math.ts',
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
// Broken example — LLM will fix it
|
|
409
|
+
code: `const greet = BROKEN (name: string) => \`Hello \${name}\``,
|
|
410
|
+
language: 'typescript',
|
|
411
|
+
filename: 'greet.ts',
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
// Unfixable example — LLM will throw
|
|
415
|
+
code: `const mystery = ???`,
|
|
416
|
+
language: 'typescript',
|
|
417
|
+
filename: 'mystery.ts',
|
|
418
|
+
},
|
|
419
|
+
]
|
|
420
|
+
|
|
421
|
+
async function main() {
|
|
422
|
+
const apiKey = process.env.LLM_API_KEY || 'sk-demo-key-12345'
|
|
423
|
+
const client = createMockLLMClient(apiKey)
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const results = await autoFixBatch(examples, client, {
|
|
427
|
+
maxAttempts: 2,
|
|
428
|
+
verbose: true,
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
console.log('\n── Batch Fix Results ──────────────────────────────')
|
|
432
|
+
for (const [index, result] of results) {
|
|
433
|
+
const label = examples[index].filename ?? `example[${index}]`
|
|
434
|
+
if (result.success) {
|
|
435
|
+
console.log(`\n✅ ${label} (${result.attempts} attempt(s))`)
|
|
436
|
+
console.log(' Fixed code:', result.fixedCode)
|
|
437
|
+
} else {
|
|
438
|
+
console.log(`\n❌ ${label} — ${result.error}`)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Summarise
|
|
443
|
+
const successCount = [...results.values()].filter((r) => r.success).length
|
|
444
|
+
console.log(`\n── Summary: ${successCount}/${examples.length} fixed successfully ──`)
|
|
445
|
+
|
|
446
|
+
// Expected output:
|
|
447
|
+
// [0] ✅ No fix needed
|
|
448
|
+
// [1] 🔧 Fixed after 2 attempt(s)
|
|
449
|
+
// [2] ❌ Could not fix
|
|
450
|
+
//
|
|
451
|
+
// ✅ math.ts (1 attempt(s)) → original code unchanged
|
|
452
|
+
// ✅ greet.ts (2 attempt(s)) → LLM-repaired code
|
|
453
|
+
// ❌ mystery.ts → LLM could not determine a fix
|
|
454
|
+
//
|
|
455
|
+
// Summary: 2/3 fixed successfully
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error('Batch fix failed unexpectedly:', error)
|
|
458
|
+
process.exit(1)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
main()
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
## `autoFixExample`
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
async function autoFixExample(example: CodeExample, client: LLMClient, options: AutoFixOptions = {}): Promise<FixResult>
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Use this to automatically repair broken or invalid code examples by iteratively applying LLM-powered fixes until the code compiles and runs correctly.
|
|
475
|
+
|
|
476
|
+
This function takes a code example that may contain errors, submits it to an LLM client for analysis and correction, and retries up to a configurable number of times — returning the fixed code along with metadata about what changed and how many attempts it took.
|
|
477
|
+
|
|
478
|
+
## Parameters
|
|
479
|
+
|
|
480
|
+
| Name | Type | Required | Description |
|
|
481
|
+
|------|------|----------|-------------|
|
|
482
|
+
| `example` | `CodeExample` | ✅ | The code example to fix, including source code, language, and any known error context |
|
|
483
|
+
| `client` | `LLMClient` | ✅ | An LLM client instance used to generate fixes (e.g., OpenAI, Anthropic wrapper) |
|
|
484
|
+
| `options` | `AutoFixOptions` | ❌ | Configuration options such as `maxIterations` (default: `3`) to control retry behavior |
|
|
485
|
+
|
|
486
|
+
## Returns
|
|
487
|
+
|
|
488
|
+
Returns a `Promise<FixResult>` that resolves with:
|
|
489
|
+
|
|
490
|
+
| Field | Type | Description |
|
|
491
|
+
|-------|------|-------------|
|
|
492
|
+
| `success` | `boolean` | Whether the example was successfully fixed |
|
|
493
|
+
| `fixedCode` | `string \| null` | The corrected source code, or `null` if fixing failed |
|
|
494
|
+
| `iterations` | `number` | How many LLM fix attempts were made |
|
|
495
|
+
| `changes` | `string[]` | A list of descriptions of what was changed |
|
|
496
|
+
| `error` | `string \| undefined` | Error message if fixing ultimately failed after all iterations |
|
|
497
|
+
|
|
498
|
+
### Example
|
|
499
|
+
|
|
500
|
+
```typescript example.ts
|
|
501
|
+
// ---- Inline type definitions (no external imports needed) ----
|
|
502
|
+
|
|
503
|
+
type CodeExample = {
|
|
504
|
+
code: string
|
|
505
|
+
language: string
|
|
506
|
+
errorMessage?: string
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
type AutoFixOptions = {
|
|
510
|
+
maxIterations?: number
|
|
511
|
+
verbose?: boolean
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
type FixResult = {
|
|
515
|
+
success: boolean
|
|
516
|
+
fixedCode: string | null
|
|
517
|
+
iterations: number
|
|
518
|
+
changes: string[]
|
|
519
|
+
error?: string
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
type LLMClient = {
|
|
523
|
+
complete: (prompt: string) => Promise<string>
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ---- Simulated autoFixExample implementation ----
|
|
527
|
+
|
|
528
|
+
async function autoFixExample(
|
|
529
|
+
example: CodeExample,
|
|
530
|
+
client: LLMClient,
|
|
531
|
+
options: AutoFixOptions = {}
|
|
532
|
+
): Promise<FixResult> {
|
|
533
|
+
const maxIterations = options.maxIterations ?? 3
|
|
534
|
+
const changes: string[] = []
|
|
535
|
+
let currentCode = example.code
|
|
536
|
+
let lastError = example.errorMessage
|
|
537
|
+
|
|
538
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
539
|
+
const prompt = [
|
|
540
|
+
`Fix the following ${example.language} code.`,
|
|
541
|
+
lastError ? `Error: ${lastError}` : '',
|
|
542
|
+
`Code:\n${currentCode}`,
|
|
543
|
+
'Return only the corrected code, no explanation.',
|
|
544
|
+
]
|
|
545
|
+
.filter(Boolean)
|
|
546
|
+
.join('\n')
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const response = await client.complete(prompt)
|
|
550
|
+
|
|
551
|
+
if (options.verbose) {
|
|
552
|
+
console.log(`[Iteration ${i + 1}] LLM response received`)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Simulate detecting a change
|
|
556
|
+
if (response !== currentCode) {
|
|
557
|
+
changes.push(`Iteration ${i + 1}: Applied LLM suggestion`)
|
|
558
|
+
currentCode = response
|
|
559
|
+
lastError = undefined // Assume fixed after each iteration
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Simulate a basic validation check (replace with real TS compile check)
|
|
563
|
+
const hasObviousError = currentCode.includes('SYNTAX_ERROR')
|
|
564
|
+
if (!hasObviousError) {
|
|
565
|
+
return {
|
|
566
|
+
success: true,
|
|
567
|
+
fixedCode: currentCode,
|
|
568
|
+
iterations: i + 1,
|
|
569
|
+
changes,
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch (err) {
|
|
573
|
+
return {
|
|
574
|
+
success: false,
|
|
575
|
+
fixedCode: null,
|
|
576
|
+
iterations: i + 1,
|
|
577
|
+
changes,
|
|
578
|
+
error: err instanceof Error ? err.message : String(err),
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
success: false,
|
|
585
|
+
fixedCode: null,
|
|
586
|
+
iterations: maxIterations,
|
|
587
|
+
changes,
|
|
588
|
+
error: `Could not fix example after ${maxIterations} iterations`,
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ---- Simulated LLM client ----
|
|
593
|
+
|
|
594
|
+
function createMockLLMClient(apiKey: string): LLMClient {
|
|
595
|
+
return {
|
|
596
|
+
complete: async (prompt: string): Promise<string> => {
|
|
597
|
+
// Simulate network latency
|
|
598
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
599
|
+
|
|
600
|
+
// Simulate fixing a missing semicolon and undefined variable
|
|
601
|
+
if (prompt.includes('const x = undefinedVar')) {
|
|
602
|
+
return `const x = 42;\nconsole.log(x);`
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return prompt.split('Code:\n')[1] ?? ''
|
|
606
|
+
},
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// ---- Main usage ----
|
|
611
|
+
|
|
612
|
+
const brokenExample: CodeExample = {
|
|
613
|
+
code: `const x = undefinedVar\nconsole.log(x)`,
|
|
614
|
+
language: 'typescript',
|
|
615
|
+
errorMessage: "ReferenceError: undefinedVar is not defined",
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function main() {
|
|
619
|
+
const client = createMockLLMClient(
|
|
620
|
+
process.env.LLM_API_KEY || 'sk-your-api-key-here'
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
const result = await autoFixExample(brokenExample, client, {
|
|
625
|
+
maxIterations: 3,
|
|
626
|
+
verbose: true,
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
if (result.success) {
|
|
630
|
+
console.log('✅ Fix succeeded!')
|
|
631
|
+
console.log('Fixed code:\n', result.fixedCode)
|
|
632
|
+
console.log('Iterations used:', result.iterations)
|
|
633
|
+
console.log('Changes applied:', result.changes)
|
|
634
|
+
// Output:
|
|
635
|
+
// ✅ Fix succeeded!
|
|
636
|
+
// Fixed code:
|
|
637
|
+
// const x = 42;
|
|
638
|
+
// console.log(x);
|
|
639
|
+
// Iterations used: 1
|
|
640
|
+
// Changes applied: [ 'Iteration 1: Applied LLM suggestion' ]
|
|
641
|
+
} else {
|
|
642
|
+
console.warn('❌ Could not fix example:', result.error)
|
|
643
|
+
console.log('Attempts made:', result.iterations)
|
|
644
|
+
}
|
|
645
|
+
} catch (error) {
|
|
646
|
+
console.error('Unexpected failure during autoFixExample:', error)
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
main()
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
## `checkPlan`
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
async function checkPlan(apiKey: string): Promise<PlanCheckResponse>
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Use this to verify an API key's validity and retrieve the associated subscription plan details before making API calls or gating features by plan tier.
|
|
663
|
+
|
|
664
|
+
## Parameters
|
|
665
|
+
|
|
666
|
+
| Name | Type | Required | Description |
|
|
667
|
+
|------|------|----------|-------------|
|
|
668
|
+
| `apiKey` | `string` | Yes | The Bearer token used to authenticate against the plan endpoint |
|
|
669
|
+
|
|
670
|
+
## Returns
|
|
671
|
+
|
|
672
|
+
Returns a `Promise<PlanCheckResponse>` that resolves with the plan details for the provided API key.
|
|
673
|
+
|
|
674
|
+
| Scenario | Result |
|
|
675
|
+
|----------|--------|
|
|
676
|
+
| Valid API key | Resolves with plan info (e.g., tier, limits, expiry) |
|
|
677
|
+
| Invalid / expired key | Rejects or resolves with an error/unauthorized response |
|
|
678
|
+
| Network failure | Rejects with a fetch error |
|
|
679
|
+
|
|
680
|
+
### Example
|
|
681
|
+
|
|
682
|
+
```typescript example.ts
|
|
683
|
+
// Inline types — no external imports needed
|
|
684
|
+
type PlanCheckResponse = {
|
|
685
|
+
plan: 'free' | 'pro' | 'enterprise'
|
|
686
|
+
valid: boolean
|
|
687
|
+
requestsPerMonth: number
|
|
688
|
+
expiresAt: string | null
|
|
689
|
+
error?: string
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Simulated implementation matching the real function's behavior
|
|
693
|
+
async function checkPlan(apiKey: string): Promise<PlanCheckResponse> {
|
|
694
|
+
const API_BASE = 'https://api.supermemory.ai'
|
|
695
|
+
|
|
696
|
+
const response = await fetch(`${API_BASE}/v1/plan`, {
|
|
697
|
+
headers: {
|
|
698
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
699
|
+
'Content-Type': 'application/json',
|
|
700
|
+
},
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
if (!response.ok) {
|
|
704
|
+
throw new Error(`Plan check failed: ${response.status} ${response.statusText}`)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return response.json() as Promise<PlanCheckResponse>
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// --- Usage ---
|
|
711
|
+
async function main() {
|
|
712
|
+
const apiKey = process.env.SUPERMEMORY_API_KEY || 'sm_your_api_key_here'
|
|
713
|
+
|
|
714
|
+
try {
|
|
715
|
+
const planInfo = await checkPlan(apiKey)
|
|
716
|
+
|
|
717
|
+
if (!planInfo.valid) {
|
|
718
|
+
console.error('API key is invalid or expired.')
|
|
719
|
+
process.exit(1)
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
console.log('Plan details:', planInfo)
|
|
723
|
+
// Expected output:
|
|
724
|
+
// Plan details: {
|
|
725
|
+
// plan: 'pro',
|
|
726
|
+
// valid: true,
|
|
727
|
+
// requestsPerMonth: 50000,
|
|
728
|
+
// expiresAt: '2025-12-31T00:00:00.000Z'
|
|
729
|
+
// }
|
|
730
|
+
|
|
731
|
+
// Gate features by plan tier
|
|
732
|
+
if (planInfo.plan === 'enterprise') {
|
|
733
|
+
console.log('Enterprise features unlocked.')
|
|
734
|
+
} else if (planInfo.plan === 'pro') {
|
|
735
|
+
console.log(`Pro plan active — ${planInfo.requestsPerMonth.toLocaleString()} requests/month.`)
|
|
736
|
+
} else {
|
|
737
|
+
console.log('Free tier — consider upgrading for higher limits.')
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
} catch (error) {
|
|
741
|
+
console.error('Failed to check plan:', error instanceof Error ? error.message : error)
|
|
742
|
+
// Handle invalid keys or network issues gracefully
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
main()
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
## `clearAuth`
|
|
753
|
+
|
|
754
|
+
```typescript
|
|
755
|
+
async function clearAuth(): Promise<void>
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
Use this to completely sign out a user by wiping all stored authentication credentials — both from the system keychain and any local auth config file.
|
|
759
|
+
|
|
760
|
+
This is the "logout" operation. It removes credentials from two places:
|
|
761
|
+
1. **System keychain** (macOS Keychain, Linux Secret Service, Windows Credential Manager)
|
|
762
|
+
2. **Local auth file** on disk (typically `~/.config/yourapp/auth.json` or similar)
|
|
763
|
+
|
|
764
|
+
After calling this, any subsequent authenticated requests will fail until the user logs in again.
|
|
765
|
+
|
|
766
|
+
### Parameters
|
|
767
|
+
|
|
768
|
+
This function takes no parameters.
|
|
769
|
+
|
|
770
|
+
### Returns
|
|
771
|
+
|
|
772
|
+
| Type | Description |
|
|
773
|
+
|------|-------------|
|
|
774
|
+
| `Promise<void>` | Resolves when all credentials have been cleared. Rejects if a critical error occurs during cleanup. |
|
|
775
|
+
|
|
776
|
+
> **Note:** If the auth file does not exist, the function completes silently without error — making it safe to call even when the user is already logged out.
|
|
777
|
+
|
|
778
|
+
### Example
|
|
779
|
+
|
|
780
|
+
```typescript example.ts
|
|
781
|
+
import { existsSync, unlinkSync, writeFileSync, mkdirSync } from 'fs'
|
|
782
|
+
import { join } from 'path'
|
|
783
|
+
import { homedir } from 'os'
|
|
784
|
+
|
|
785
|
+
// --- Inline simulation of keychain operations ---
|
|
786
|
+
const inMemoryKeychain: Record<string, string> = {
|
|
787
|
+
'myapp/auth': 'super-secret-token-abc123'
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
async function keychainDelete(): Promise<void> {
|
|
791
|
+
delete inMemoryKeychain['myapp/auth']
|
|
792
|
+
console.log('[keychain] Credentials removed from system keychain')
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// --- Inline simulation of clearAuth ---
|
|
796
|
+
const AUTH_FILE = join(homedir(), '.config', 'myapp-example', 'auth.json')
|
|
797
|
+
|
|
798
|
+
async function clearAuth(): Promise<void> {
|
|
799
|
+
// Step 1: Remove from system keychain
|
|
800
|
+
await keychainDelete()
|
|
801
|
+
|
|
802
|
+
// Step 2: Remove local auth file if it exists
|
|
803
|
+
if (existsSync(AUTH_FILE)) {
|
|
804
|
+
try {
|
|
805
|
+
unlinkSync(AUTH_FILE)
|
|
806
|
+
console.log(`[auth] Removed auth file: ${AUTH_FILE}`)
|
|
807
|
+
} catch (err) {
|
|
808
|
+
console.warn(`[auth] Could not remove auth file: ${(err as Error).message}`)
|
|
809
|
+
}
|
|
810
|
+
} else {
|
|
811
|
+
console.log('[auth] No auth file found — nothing to remove')
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// --- Setup: write a fake auth file to demonstrate deletion ---
|
|
816
|
+
function setupFakeAuthFile(): void {
|
|
817
|
+
const dir = join(homedir(), '.config', 'myapp-example')
|
|
818
|
+
mkdirSync(dir, { recursive: true })
|
|
819
|
+
writeFileSync(
|
|
820
|
+
AUTH_FILE,
|
|
821
|
+
JSON.stringify({ token: 'super-secret-token-abc123', userId: 'usr_9f3kd82' }, null, 2),
|
|
822
|
+
{ mode: 0o600 }
|
|
823
|
+
)
|
|
824
|
+
console.log(`[setup] Created fake auth file at: ${AUTH_FILE}`)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// --- Main: demonstrate clearAuth ---
|
|
828
|
+
async function main() {
|
|
829
|
+
try {
|
|
830
|
+
setupFakeAuthFile()
|
|
831
|
+
|
|
832
|
+
console.log('\nBefore clearAuth:')
|
|
833
|
+
console.log(' Auth file exists:', existsSync(AUTH_FILE))
|
|
834
|
+
console.log(' Keychain entry exists:', 'myapp/auth' in inMemoryKeychain)
|
|
835
|
+
|
|
836
|
+
console.log('\nCalling clearAuth()...')
|
|
837
|
+
await clearAuth()
|
|
838
|
+
|
|
839
|
+
console.log('\nAfter clearAuth:')
|
|
840
|
+
console.log(' Auth file exists:', existsSync(AUTH_FILE))
|
|
841
|
+
console.log(' Keychain entry exists:', 'myapp/auth' in inMemoryKeychain)
|
|
842
|
+
|
|
843
|
+
// Expected output:
|
|
844
|
+
// Before clearAuth:
|
|
845
|
+
// Auth file exists: true
|
|
846
|
+
// Keychain entry exists: true
|
|
847
|
+
// Calling clearAuth()...
|
|
848
|
+
// [keychain] Credentials removed from system keychain
|
|
849
|
+
// [auth] Removed auth file: /home/user/.config/myapp-example/auth.json
|
|
850
|
+
// After clearAuth:
|
|
851
|
+
// Auth file exists: false
|
|
852
|
+
// Keychain entry exists: false
|
|
853
|
+
} catch (error) {
|
|
854
|
+
console.error('Failed to clear auth:', (error as Error).message)
|
|
855
|
+
process.exit(1)
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
main()
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
### Related
|
|
863
|
+
|
|
864
|
+
<CardGroup cols={3}>
|
|
865
|
+
<Card title="keychainDelete" icon="link" href="#keychaindelete">
|
|
866
|
+
Uses
|
|
867
|
+
</Card>
|
|
868
|
+
</CardGroup>
|
|
869
|
+
|
|
870
|
+
---
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
## `createLLMClient`
|
|
874
|
+
|
|
875
|
+
```typescript
|
|
876
|
+
function createLLMClient(config: {
|
|
877
|
+
provider: LLMProvider
|
|
878
|
+
model: string
|
|
879
|
+
baseUrl?: string
|
|
880
|
+
timeout?: number
|
|
881
|
+
maxRetries?: number
|
|
882
|
+
}): LLMClient
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
Use this to initialize a typed LLM client for a specific AI provider (OpenAI, Anthropic, etc.) with built-in retry and timeout handling.
|
|
886
|
+
|
|
887
|
+
This is your entry point for all LLM interactions — call it once at startup, then reuse the returned client across your application to send prompts and receive completions.
|
|
888
|
+
|
|
889
|
+
## Parameters
|
|
890
|
+
|
|
891
|
+
| Name | Type | Required | Description |
|
|
892
|
+
|------|------|----------|-------------|
|
|
893
|
+
| `config.provider` | `LLMProvider` | ✅ Yes | The AI provider to use. Accepted values: `"openai"`, `"anthropic"`, `"azure"` |
|
|
894
|
+
| `config.model` | `string` | ✅ Yes | The model identifier to target (e.g. `"gpt-4o"`, `"claude-3-5-sonnet-20241022"`) |
|
|
895
|
+
| `config.baseUrl` | `string` | No | Override the default API endpoint. Useful for proxies, local models, or Azure deployments |
|
|
896
|
+
| `config.timeout` | `number` | No | Request timeout in milliseconds. Defaults to `30000` (30s) |
|
|
897
|
+
| `config.maxRetries` | `number` | No | Number of times to retry on transient failures. Defaults to `2` |
|
|
898
|
+
|
|
899
|
+
## Returns
|
|
900
|
+
|
|
901
|
+
Returns an `LLMClient` instance with methods to send chat/completion requests to the configured provider. The client is pre-configured with your credentials (read from environment variables), timeout, and retry logic.
|
|
902
|
+
|
|
903
|
+
> **Credentials:** The client automatically reads the appropriate API key from your environment (e.g. `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`) based on the `provider` value.
|
|
904
|
+
|
|
905
|
+
### Example
|
|
906
|
+
|
|
907
|
+
```typescript example.ts
|
|
908
|
+
// --- Inline types (mirrors the real library's shape) ---
|
|
909
|
+
type LLMProvider = "openai" | "anthropic" | "azure"
|
|
910
|
+
|
|
911
|
+
interface Message {
|
|
912
|
+
role: "user" | "assistant" | "system"
|
|
913
|
+
content: string
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
interface CompletionResponse {
|
|
917
|
+
id: string
|
|
918
|
+
content: string
|
|
919
|
+
model: string
|
|
920
|
+
usage: { promptTokens: number; completionTokens: number; totalTokens: number }
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
interface LLMClient {
|
|
924
|
+
complete(messages: Message[]): Promise<CompletionResponse>
|
|
925
|
+
provider: LLMProvider
|
|
926
|
+
model: string
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
interface LLMClientConfig {
|
|
930
|
+
provider: LLMProvider
|
|
931
|
+
model: string
|
|
932
|
+
baseUrl?: string
|
|
933
|
+
timeout?: number
|
|
934
|
+
maxRetries?: number
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// --- Simulated createLLMClient (self-contained, no external imports) ---
|
|
938
|
+
function createLLMClient(config: LLMClientConfig): LLMClient {
|
|
939
|
+
const { provider, model, timeout = 30000, maxRetries = 2 } = config
|
|
940
|
+
|
|
941
|
+
const apiKeyMap: Record<LLMProvider, string> = {
|
|
942
|
+
openai: process.env.OPENAI_API_KEY || "sk-your-openai-key",
|
|
943
|
+
anthropic: process.env.ANTHROPIC_API_KEY || "sk-ant-your-anthropic-key",
|
|
944
|
+
azure: process.env.AZURE_OPENAI_API_KEY || "your-azure-key",
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const apiKey = apiKeyMap[provider]
|
|
948
|
+
if (!apiKey) {
|
|
949
|
+
throw new Error(`No API key found for provider: ${provider}`)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
console.log(`[createLLMClient] Initialized ${provider} client`)
|
|
953
|
+
console.log(` Model: ${model}`)
|
|
954
|
+
console.log(` Base URL: ${config.baseUrl ?? "(default)"}`)
|
|
955
|
+
console.log(` Timeout: ${timeout}ms`)
|
|
956
|
+
console.log(` Max Retries: ${maxRetries}`)
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
provider,
|
|
960
|
+
model,
|
|
961
|
+
async complete(messages: Message[]): Promise<CompletionResponse> {
|
|
962
|
+
// Simulated response — real client would call the provider's API here
|
|
963
|
+
return {
|
|
964
|
+
id: "chatcmpl-abc123",
|
|
965
|
+
content: `Simulated response from ${provider}/${model} to: "${messages.at(-1)?.content}"`,
|
|
966
|
+
model,
|
|
967
|
+
usage: { promptTokens: 12, completionTokens: 28, totalTokens: 40 },
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// --- Usage ---
|
|
974
|
+
async function main() {
|
|
975
|
+
try {
|
|
976
|
+
// Basic OpenAI setup
|
|
977
|
+
const openaiClient = createLLMClient({
|
|
978
|
+
provider: "openai",
|
|
979
|
+
model: "gpt-4o",
|
|
980
|
+
timeout: 15000,
|
|
981
|
+
maxRetries: 3,
|
|
982
|
+
})
|
|
983
|
+
|
|
984
|
+
const response = await openaiClient.complete([
|
|
985
|
+
{ role: "system", content: "You are a helpful assistant." },
|
|
986
|
+
{ role: "user", content: "What is the capital of France?" },
|
|
987
|
+
])
|
|
988
|
+
|
|
989
|
+
console.log("\n--- Completion Response ---")
|
|
990
|
+
console.log("ID: ", response.id)
|
|
991
|
+
console.log("Content: ", response.content)
|
|
992
|
+
console.log("Tokens: ", response.usage.totalTokens)
|
|
993
|
+
// Output:
|
|
994
|
+
// ID: chatcmpl-abc123
|
|
995
|
+
// Content: Simulated response from openai/gpt-4o to: "What is the capital of France?"
|
|
996
|
+
// Tokens: 40
|
|
997
|
+
|
|
998
|
+
// Anthropic with a custom proxy base URL
|
|
999
|
+
const anthropicClient = createLLMClient({
|
|
1000
|
+
provider: "anthropic",
|
|
1001
|
+
model: "claude-3-5-sonnet-20241022",
|
|
1002
|
+
baseUrl: "https://my-proxy.internal/anthropic",
|
|
1003
|
+
timeout: 20000,
|
|
1004
|
+
})
|
|
1005
|
+
|
|
1006
|
+
console.log(`\nAnthropic client ready: ${anthropicClient.provider}/${anthropicClient.model}`)
|
|
1007
|
+
// Output: Anthropic client ready: anthropic/claude-3-5-sonnet-20241022
|
|
1008
|
+
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
console.error("Failed to create or use LLM client:", error)
|
|
1011
|
+
process.exit(1)
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
main()
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
---
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
## `createPythonValidator`
|
|
1022
|
+
|
|
1023
|
+
```typescript
|
|
1024
|
+
function createPythonValidator(): (code: string) => Promise<ValidationResult>
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
Use this to validate Python code syntax before executing or storing it — catches syntax errors without running the code.
|
|
1028
|
+
|
|
1029
|
+
`createPythonValidator` returns a reusable validator function that writes code to a temporary file and checks it with `python3 -m py_compile`. Requires `python3` to be available in your system `PATH`.
|
|
1030
|
+
|
|
1031
|
+
## Parameters
|
|
1032
|
+
|
|
1033
|
+
The factory function takes no parameters. The returned validator accepts:
|
|
1034
|
+
|
|
1035
|
+
| Name | Type | Required | Description |
|
|
1036
|
+
|------|------|----------|-------------|
|
|
1037
|
+
| `code` | `string` | ✅ | The Python source code string to validate |
|
|
1038
|
+
|
|
1039
|
+
## Returns
|
|
1040
|
+
|
|
1041
|
+
**`createPythonValidator()`** → `(code: string) => Promise<ValidationResult>`
|
|
1042
|
+
|
|
1043
|
+
Returns a validator function. Each call to that validator returns a `Promise<ValidationResult>`:
|
|
1044
|
+
|
|
1045
|
+
| Scenario | Result shape |
|
|
1046
|
+
|----------|-------------|
|
|
1047
|
+
| Valid Python syntax | `{ valid: true, errors: [] }` |
|
|
1048
|
+
| Syntax error detected | `{ valid: false, errors: [{ message: string, line?: number }] }` |
|
|
1049
|
+
| `python3` not found in PATH | `{ valid: false, errors: [{ message: 'python3 not found...' }] }` |
|
|
1050
|
+
|
|
1051
|
+
> **Requirement:** `python3` must be installed and accessible via `PATH`. The validator uses `python3 -m py_compile` internally and cleans up any temporary files after each check.
|
|
1052
|
+
|
|
1053
|
+
### Example
|
|
1054
|
+
|
|
1055
|
+
```typescript example.ts
|
|
1056
|
+
import { spawnSync } from 'child_process'
|
|
1057
|
+
import { writeFileSync, unlinkSync } from 'fs'
|
|
1058
|
+
import { join } from 'path'
|
|
1059
|
+
import { tmpdir } from 'os'
|
|
1060
|
+
|
|
1061
|
+
// Inline the ValidationResult type
|
|
1062
|
+
type ValidationError = {
|
|
1063
|
+
message: string
|
|
1064
|
+
line?: number
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
type ValidationResult = {
|
|
1068
|
+
valid: boolean
|
|
1069
|
+
errors: ValidationError[]
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Self-contained implementation of createPythonValidator
|
|
1073
|
+
function createPythonValidator(): (code: string) => Promise<ValidationResult> {
|
|
1074
|
+
return async (code: string): Promise<ValidationResult> => {
|
|
1075
|
+
const tmpFile = join(tmpdir(), `py_validate_${Date.now()}.py`)
|
|
1076
|
+
|
|
1077
|
+
try {
|
|
1078
|
+
writeFileSync(tmpFile, code, 'utf8')
|
|
1079
|
+
|
|
1080
|
+
const result = spawnSync('python3', ['-m', 'py_compile', tmpFile], {
|
|
1081
|
+
encoding: 'utf8',
|
|
1082
|
+
timeout: 10_000,
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
if (result.error) {
|
|
1086
|
+
// python3 binary not found or failed to spawn
|
|
1087
|
+
return {
|
|
1088
|
+
valid: false,
|
|
1089
|
+
errors: [{ message: `python3 not found or failed to run: ${result.error.message}` }],
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
if (result.status !== 0) {
|
|
1094
|
+
// Parse error output like: " File "/tmp/py_validate.py", line 2\n ..."
|
|
1095
|
+
const stderr = result.stderr || ''
|
|
1096
|
+
const lineMatch = stderr.match(/line (\d+)/)
|
|
1097
|
+
const line = lineMatch ? parseInt(lineMatch[1], 10) : undefined
|
|
1098
|
+
|
|
1099
|
+
// Strip the temp file path from the message for cleaner output
|
|
1100
|
+
const cleanMessage = stderr.replace(tmpFile, '<code>').trim()
|
|
1101
|
+
|
|
1102
|
+
return {
|
|
1103
|
+
valid: false,
|
|
1104
|
+
errors: [{ message: cleanMessage, line }],
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
return { valid: true, errors: [] }
|
|
1109
|
+
} finally {
|
|
1110
|
+
// Always clean up the temp file
|
|
1111
|
+
try {
|
|
1112
|
+
unlinkSync(tmpFile)
|
|
1113
|
+
} catch {
|
|
1114
|
+
// Ignore cleanup errors
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// --- Usage Example ---
|
|
1121
|
+
|
|
1122
|
+
async function main() {
|
|
1123
|
+
const validate = createPythonValidator()
|
|
1124
|
+
|
|
1125
|
+
// ✅ Valid Python code
|
|
1126
|
+
const validCode = `
|
|
1127
|
+
def greet(name: str) -> str:
|
|
1128
|
+
return f"Hello, {name}!"
|
|
1129
|
+
|
|
1130
|
+
print(greet("world"))
|
|
1131
|
+
`.trim()
|
|
1132
|
+
|
|
1133
|
+
const validResult = await validate(validCode)
|
|
1134
|
+
console.log('Valid code result:', validResult)
|
|
1135
|
+
// Output: { valid: true, errors: [] }
|
|
1136
|
+
|
|
1137
|
+
// ❌ Invalid Python code (missing colon after def)
|
|
1138
|
+
const invalidCode = `
|
|
1139
|
+
def greet(name)
|
|
1140
|
+
return "Hello, " + name
|
|
1141
|
+
`.trim()
|
|
1142
|
+
|
|
1143
|
+
const invalidResult = await validate(invalidCode)
|
|
1144
|
+
console.log('Invalid code result:', invalidResult)
|
|
1145
|
+
// Output: { valid: false, errors: [{ message: "...: invalid syntax (<code>, line 1)", line: 1 }] }
|
|
1146
|
+
|
|
1147
|
+
// Reuse the same validator for multiple checks (efficient)
|
|
1148
|
+
const snippets = [
|
|
1149
|
+
'x = [i**2 for i in range(10)]', // valid
|
|
1150
|
+
'if True print("oops")', // invalid - missing colon
|
|
1151
|
+
'import os\nprint(os.getcwd())', // valid
|
|
1152
|
+
]
|
|
1153
|
+
|
|
1154
|
+
console.log('\nBatch validation:')
|
|
1155
|
+
for (const snippet of snippets) {
|
|
1156
|
+
const { valid, errors } = await validate(snippet)
|
|
1157
|
+
console.log(` "${snippet.slice(0, 30)}..." → ${valid ? '✅ valid' : `❌ ${errors[0]?.message}`}`)
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
main().catch(console.error)
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
---
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
## `createTypeScriptValidator`
|
|
1168
|
+
|
|
1169
|
+
```typescript
|
|
1170
|
+
function createTypeScriptValidator(): (code: string) => Promise<ValidationResult>
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
Use this to validate TypeScript code strings at runtime — perfect for checking LLM-generated code, user-submitted snippets, or dynamically built TypeScript before execution.
|
|
1174
|
+
|
|
1175
|
+
Returns a validator function you call with any TypeScript code string. The validator uses the TypeScript compiler (`tsc`) under the hood to catch type errors, syntax issues, and compilation failures.
|
|
1176
|
+
|
|
1177
|
+
## Parameters
|
|
1178
|
+
|
|
1179
|
+
This factory function takes no parameters.
|
|
1180
|
+
|
|
1181
|
+
## Returns
|
|
1182
|
+
|
|
1183
|
+
| Value | Type | Description |
|
|
1184
|
+
|---|---|---|
|
|
1185
|
+
| `validator` | `(code: string) => Promise<ValidationResult>` | A reusable async function that accepts a TypeScript code string and resolves to a `ValidationResult` |
|
|
1186
|
+
|
|
1187
|
+
### `ValidationResult` Shape
|
|
1188
|
+
|
|
1189
|
+
| Field | Type | Description |
|
|
1190
|
+
|---|---|---|
|
|
1191
|
+
| `valid` | `boolean` | `true` if the code compiled without errors |
|
|
1192
|
+
| `errors` | `string[]` | List of compiler error messages; empty array when `valid` is `true` |
|
|
1193
|
+
|
|
1194
|
+
## Behavior
|
|
1195
|
+
|
|
1196
|
+
- **Valid code** → resolves with `{ valid: true, errors: [] }`
|
|
1197
|
+
- **Invalid code** → resolves with `{ valid: false, errors: ["TS2345: ...", ...] }`
|
|
1198
|
+
- **Runtime failure** (e.g. `typescript` not installed) → resolves with `{ valid: false, errors: ["<internal error message>"] }`
|
|
1199
|
+
|
|
1200
|
+
> **Tip:** Call `createTypeScriptValidator()` once and reuse the returned function across multiple validations to avoid repeated setup overhead.
|
|
1201
|
+
|
|
1202
|
+
### Example
|
|
1203
|
+
|
|
1204
|
+
```typescript example.ts
|
|
1205
|
+
import { transpileModule, type TranspileOptions } from 'typescript'
|
|
1206
|
+
|
|
1207
|
+
// --- Inline types (mirrors the real ValidationResult) ---
|
|
1208
|
+
interface ValidationResult {
|
|
1209
|
+
valid: boolean
|
|
1210
|
+
errors: string[]
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// --- Self-contained implementation mirroring the real function ---
|
|
1214
|
+
function createTypeScriptValidator(): (code: string) => Promise<ValidationResult> {
|
|
1215
|
+
return async (code: string): Promise<ValidationResult> => {
|
|
1216
|
+
try {
|
|
1217
|
+
const options: TranspileOptions = {
|
|
1218
|
+
compilerOptions: {
|
|
1219
|
+
strict: true,
|
|
1220
|
+
noImplicitAny: true,
|
|
1221
|
+
},
|
|
1222
|
+
reportDiagnostics: true,
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const result = transpileModule(code, options)
|
|
1226
|
+
|
|
1227
|
+
const errors =
|
|
1228
|
+
result.diagnostics
|
|
1229
|
+
?.filter((d) => d.messageText)
|
|
1230
|
+
.map((d) =>
|
|
1231
|
+
typeof d.messageText === 'string'
|
|
1232
|
+
? d.messageText
|
|
1233
|
+
: d.messageText.messageText
|
|
1234
|
+
) ?? []
|
|
1235
|
+
|
|
1236
|
+
return {
|
|
1237
|
+
valid: errors.length === 0,
|
|
1238
|
+
errors,
|
|
1239
|
+
}
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
return {
|
|
1242
|
+
valid: false,
|
|
1243
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// --- Usage ---
|
|
1250
|
+
const validateTypeScript = createTypeScriptValidator()
|
|
1251
|
+
|
|
1252
|
+
async function main() {
|
|
1253
|
+
try {
|
|
1254
|
+
// ✅ Valid TypeScript
|
|
1255
|
+
const validCode = `
|
|
1256
|
+
const greet = (name: string): string => {
|
|
1257
|
+
return \`Hello, \${name}!\`
|
|
1258
|
+
}
|
|
1259
|
+
console.log(greet('world'))
|
|
1260
|
+
`
|
|
1261
|
+
|
|
1262
|
+
const validResult = await validateTypeScript(validCode)
|
|
1263
|
+
console.log('Valid code result:', validResult)
|
|
1264
|
+
// Output: { valid: true, errors: [] }
|
|
1265
|
+
|
|
1266
|
+
// ❌ Invalid TypeScript — wrong argument type
|
|
1267
|
+
const invalidCode = `
|
|
1268
|
+
const add = (a: number, b: number): number => a + b
|
|
1269
|
+
const result: number = add("not", "numbers")
|
|
1270
|
+
`
|
|
1271
|
+
|
|
1272
|
+
const invalidResult = await validateTypeScript(invalidCode)
|
|
1273
|
+
console.log('Invalid code result:', invalidResult)
|
|
1274
|
+
// Output: { valid: false, errors: ["Argument of type 'string' is not assignable..."] }
|
|
1275
|
+
|
|
1276
|
+
// ❌ Syntax error
|
|
1277
|
+
const brokenCode = `
|
|
1278
|
+
function broken( {
|
|
1279
|
+
return 42
|
|
1280
|
+
`
|
|
1281
|
+
|
|
1282
|
+
const brokenResult = await validateTypeScript(brokenCode)
|
|
1283
|
+
console.log('Broken code result:', brokenResult)
|
|
1284
|
+
// Output: { valid: false, errors: ["',' expected.", ...] }
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
console.error('Unexpected failure:', error)
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
main()
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
---
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
## `definePlugin`
|
|
1297
|
+
|
|
1298
|
+
```typescript
|
|
1299
|
+
function definePlugin(plugin: SkryptPlugin): SkryptPlugin
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
Use this to define a typed plugin configuration object for the Skrypt plugin system with full type safety and IDE autocompletion.
|
|
1303
|
+
|
|
1304
|
+
This is an identity function that acts as a **type helper** — it takes your plugin definition and returns it unchanged, but ensures your object conforms to the `SkryptPlugin` interface at compile time. Think of it as the Skrypt equivalent of Vue's `defineComponent` or Vite's `defineConfig`.
|
|
1305
|
+
|
|
1306
|
+
## Parameters
|
|
1307
|
+
|
|
1308
|
+
| Name | Type | Required | Description |
|
|
1309
|
+
|------|------|----------|-------------|
|
|
1310
|
+
| `plugin` | `SkryptPlugin` | ✅ | The plugin configuration object to validate and return |
|
|
1311
|
+
|
|
1312
|
+
## Returns
|
|
1313
|
+
|
|
1314
|
+
Returns the same `SkryptPlugin` object passed in, unchanged. The value is identical to the input — the benefit is purely type-level validation and editor tooling support.
|
|
1315
|
+
|
|
1316
|
+
**When to use this vs a plain object literal:**
|
|
1317
|
+
- Use `definePlugin(...)` when you want TypeScript to catch missing/invalid fields at the definition site
|
|
1318
|
+
- Use `definePlugin(...)` when you want autocomplete on plugin properties in your editor
|
|
1319
|
+
- A plain object works at runtime, but you lose type checking on the shape
|
|
1320
|
+
|
|
1321
|
+
### Example
|
|
1322
|
+
|
|
1323
|
+
```typescript example.ts
|
|
1324
|
+
// Inline the SkryptPlugin type (do not import from autodocs)
|
|
1325
|
+
type PluginHookContext = {
|
|
1326
|
+
cwd: string
|
|
1327
|
+
config: Record<string, unknown>
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
type SkryptPlugin = {
|
|
1331
|
+
name: string
|
|
1332
|
+
version?: string
|
|
1333
|
+
description?: string
|
|
1334
|
+
setup?: (context: PluginHookContext) => void | Promise<void>
|
|
1335
|
+
teardown?: () => void | Promise<void>
|
|
1336
|
+
hooks?: {
|
|
1337
|
+
beforeBuild?: () => void | Promise<void>
|
|
1338
|
+
afterBuild?: () => void | Promise<void>
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// Identity function — validates shape at compile time, returns plugin unchanged
|
|
1343
|
+
function definePlugin(plugin: SkryptPlugin): SkryptPlugin {
|
|
1344
|
+
return plugin
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// --- Define a plugin using the helper ---
|
|
1348
|
+
|
|
1349
|
+
const myAnalyticsPlugin = definePlugin({
|
|
1350
|
+
name: 'analytics-reporter',
|
|
1351
|
+
version: '1.2.0',
|
|
1352
|
+
description: 'Reports build metrics to an analytics endpoint',
|
|
1353
|
+
|
|
1354
|
+
setup: async (context) => {
|
|
1355
|
+
console.log(`[analytics-reporter] Initializing in: ${context.cwd}`)
|
|
1356
|
+
console.log(`[analytics-reporter] Config:`, context.config)
|
|
1357
|
+
},
|
|
1358
|
+
|
|
1359
|
+
hooks: {
|
|
1360
|
+
beforeBuild: async () => {
|
|
1361
|
+
console.log('[analytics-reporter] Build starting — recording timestamp')
|
|
1362
|
+
},
|
|
1363
|
+
afterBuild: async () => {
|
|
1364
|
+
const endpoint = process.env.ANALYTICS_URL || 'https://analytics.example.com/build'
|
|
1365
|
+
console.log(`[analytics-reporter] Sending metrics to ${endpoint}`)
|
|
1366
|
+
},
|
|
1367
|
+
},
|
|
1368
|
+
|
|
1369
|
+
teardown: async () => {
|
|
1370
|
+
console.log('[analytics-reporter] Cleaning up resources')
|
|
1371
|
+
},
|
|
1372
|
+
})
|
|
1373
|
+
|
|
1374
|
+
// --- Simulate plugin lifecycle ---
|
|
1375
|
+
|
|
1376
|
+
async function runPluginLifecycle(plugin: SkryptPlugin) {
|
|
1377
|
+
try {
|
|
1378
|
+
console.log(`\nLoading plugin: "${plugin.name}" v${plugin.version ?? 'unknown'}`)
|
|
1379
|
+
console.log(`Description: ${plugin.description ?? 'N/A'}`)
|
|
1380
|
+
|
|
1381
|
+
const context: PluginHookContext = {
|
|
1382
|
+
cwd: process.cwd(),
|
|
1383
|
+
config: { outputDir: 'dist', minify: true },
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
await plugin.setup?.(context)
|
|
1387
|
+
await plugin.hooks?.beforeBuild?.()
|
|
1388
|
+
await plugin.hooks?.afterBuild?.()
|
|
1389
|
+
await plugin.teardown?.()
|
|
1390
|
+
|
|
1391
|
+
console.log(`\nPlugin "${plugin.name}" lifecycle complete.`)
|
|
1392
|
+
// Output:
|
|
1393
|
+
// Loading plugin: "analytics-reporter" v1.2.0
|
|
1394
|
+
// Description: Reports build metrics to an analytics endpoint
|
|
1395
|
+
// [analytics-reporter] Initializing in: /your/project
|
|
1396
|
+
// [analytics-reporter] Config: { outputDir: 'dist', minify: true }
|
|
1397
|
+
// [analytics-reporter] Build starting — recording timestamp
|
|
1398
|
+
// [analytics-reporter] Sending metrics to https://analytics.example.com/build
|
|
1399
|
+
// [analytics-reporter] Cleaning up resources
|
|
1400
|
+
// Plugin "analytics-reporter" lifecycle complete.
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
console.error(`Plugin "${plugin.name}" failed:`, error)
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
runPluginLifecycle(myAnalyticsPlugin)
|
|
1407
|
+
```
|
|
1408
|
+
|
|
1409
|
+
---
|
|
1410
|
+
|
|
1411
|
+
|
|
1412
|
+
## `fixCodeSample`
|
|
1413
|
+
|
|
1414
|
+
```typescript
|
|
1415
|
+
async function fixCodeSample(client: LLMClient, code: string, error: string, context: string, iteration: number = 1): Promise<string>
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
Use this to automatically repair broken code samples by sending them to an LLM with the error context and receiving a corrected version. Ideal for documentation pipelines, code generation workflows, or CI systems that validate and self-heal code examples.
|
|
1419
|
+
|
|
1420
|
+
## Parameters
|
|
1421
|
+
|
|
1422
|
+
| Name | Type | Required | Description |
|
|
1423
|
+
|------|------|----------|-------------|
|
|
1424
|
+
| `client` | `LLMClient` | ✅ | An initialized LLM client instance used to generate the fix |
|
|
1425
|
+
| `code` | `string` | ✅ | The broken code sample that needs to be repaired |
|
|
1426
|
+
| `error` | `string` | ✅ | The error message or stack trace produced by the broken code |
|
|
1427
|
+
| `context` | `string` | ✅ | Additional context about what the code is supposed to do (e.g., function docs, expected behavior) |
|
|
1428
|
+
| `iteration` | `number` | ❌ | Which fix attempt this is (default: `1`). Used to apply progressively smarter strategies on repeated failures |
|
|
1429
|
+
|
|
1430
|
+
## Returns
|
|
1431
|
+
|
|
1432
|
+
Returns a `Promise<string>` containing the corrected code sample. The returned string is the fixed code only — ready to replace the original broken sample.
|
|
1433
|
+
|
|
1434
|
+
## Notes
|
|
1435
|
+
|
|
1436
|
+
- The `iteration` parameter enables escalating fix strategies: pass `2` or `3` on retry loops to signal that simpler fixes have already been attempted
|
|
1437
|
+
- The `context` parameter significantly improves fix quality — include the function signature, docstring, and expected output
|
|
1438
|
+
- The returned code may still need validation; use this inside a retry loop with a code runner for best results
|
|
1439
|
+
|
|
1440
|
+
### Example
|
|
1441
|
+
|
|
1442
|
+
```typescript example.ts
|
|
1443
|
+
// ── Inline types (do not import from autodocs) ──────────────────────────────
|
|
1444
|
+
interface LLMMessage {
|
|
1445
|
+
role: 'system' | 'user' | 'assistant'
|
|
1446
|
+
content: string
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
interface LLMClient {
|
|
1450
|
+
complete(messages: LLMMessage[]): Promise<string>
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// ── Simulated LLM client (replace with your real client in production) ───────
|
|
1454
|
+
function createMockLLMClient(apiKey: string): LLMClient {
|
|
1455
|
+
return {
|
|
1456
|
+
async complete(messages: LLMMessage[]): Promise<string> {
|
|
1457
|
+
// In production this would call OpenAI / Anthropic / etc.
|
|
1458
|
+
console.log(`[LLMClient] Sending ${messages.length} messages to API...`)
|
|
1459
|
+
console.log(`[LLMClient] Using API key: ${apiKey.slice(0, 8)}...`)
|
|
1460
|
+
|
|
1461
|
+
// Simulate a fixed version of the broken code
|
|
1462
|
+
return `
|
|
1463
|
+
async function fetchUser(userId: string) {
|
|
1464
|
+
try {
|
|
1465
|
+
const response = await fetch(\`https://api.example.com/users/\${userId}\`)
|
|
1466
|
+
if (!response.ok) throw new Error(\`HTTP error: \${response.status}\`)
|
|
1467
|
+
const data = await response.json()
|
|
1468
|
+
console.log('User:', data)
|
|
1469
|
+
return data
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
console.error('Failed to fetch user:', error)
|
|
1472
|
+
throw error
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
fetchUser('user_abc123')
|
|
1477
|
+
`.trim()
|
|
1478
|
+
},
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// ── Inline implementation of fixCodeSample ───────────────────────────────────
|
|
1483
|
+
async function fixCodeSample(
|
|
1484
|
+
client: LLMClient,
|
|
1485
|
+
code: string,
|
|
1486
|
+
error: string,
|
|
1487
|
+
context: string,
|
|
1488
|
+
iteration: number = 1
|
|
1489
|
+
): Promise<string> {
|
|
1490
|
+
const strategyHint =
|
|
1491
|
+
iteration === 1
|
|
1492
|
+
? 'Fix the specific error shown.'
|
|
1493
|
+
: iteration === 2
|
|
1494
|
+
? 'The previous fix did not work. Try a different approach and simplify the code.'
|
|
1495
|
+
: 'Multiple fixes have failed. Rewrite the example from scratch using only the context provided.'
|
|
1496
|
+
|
|
1497
|
+
const messages: LLMMessage[] = [
|
|
1498
|
+
{
|
|
1499
|
+
role: 'system',
|
|
1500
|
+
content:
|
|
1501
|
+
'You are an expert code repair assistant. Return ONLY the corrected code with no explanation, markdown fences, or commentary.',
|
|
1502
|
+
},
|
|
1503
|
+
{
|
|
1504
|
+
role: 'user',
|
|
1505
|
+
content: `
|
|
1506
|
+
## Context
|
|
1507
|
+
${context}
|
|
1508
|
+
|
|
1509
|
+
## Broken Code
|
|
1510
|
+
\`\`\`
|
|
1511
|
+
${code}
|
|
1512
|
+
\`\`\`
|
|
1513
|
+
|
|
1514
|
+
## Error
|
|
1515
|
+
\`\`\`
|
|
1516
|
+
${error}
|
|
1517
|
+
\`\`\`
|
|
1518
|
+
|
|
1519
|
+
## Instructions
|
|
1520
|
+
Fix attempt #${iteration}. ${strategyHint}
|
|
1521
|
+
Return only the corrected code.
|
|
1522
|
+
`.trim(),
|
|
1523
|
+
},
|
|
1524
|
+
]
|
|
1525
|
+
|
|
1526
|
+
const fixed = await client.complete(messages)
|
|
1527
|
+
return fixed.trim()
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// ── Example usage ─────────────────────────────────────────────────────────────
|
|
1531
|
+
const brokenCode = `
|
|
1532
|
+
async function fetchUser(userId: string) {
|
|
1533
|
+
const response = await fetch('https://api.example.com/users/' + userId)
|
|
1534
|
+
const data = response.json() // ← missing await
|
|
1535
|
+
console.log('User:', data)
|
|
1536
|
+
return data
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
fetchUser('user_abc123')
|
|
1540
|
+
`.trim()
|
|
1541
|
+
|
|
1542
|
+
const errorMessage = `
|
|
1543
|
+
TypeError: data.name is undefined
|
|
1544
|
+
at fetchUser (example.ts:4:22)
|
|
1545
|
+
`.trim()
|
|
1546
|
+
|
|
1547
|
+
const contextDescription = `
|
|
1548
|
+
fetchUser(userId: string): Promise<User>
|
|
1549
|
+
Fetches a user object from the REST API by ID.
|
|
1550
|
+
Expected output: logs the user object and returns it.
|
|
1551
|
+
The function must handle HTTP errors and use proper async/await.
|
|
1552
|
+
`.trim()
|
|
1553
|
+
|
|
1554
|
+
async function main() {
|
|
1555
|
+
const apiKey = process.env.OPENAI_API_KEY || 'sk-your-api-key-here'
|
|
1556
|
+
const client = createMockLLMClient(apiKey)
|
|
1557
|
+
|
|
1558
|
+
try {
|
|
1559
|
+
console.log('=== Attempt 1 ===')
|
|
1560
|
+
const fixedCode = await fixCodeSample(
|
|
1561
|
+
client,
|
|
1562
|
+
brokenCode,
|
|
1563
|
+
errorMessage,
|
|
1564
|
+
contextDescription,
|
|
1565
|
+
1 // first attempt — targeted fix
|
|
1566
|
+
)
|
|
1567
|
+
console.log('\nFixed code:\n')
|
|
1568
|
+
console.log(fixedCode)
|
|
1569
|
+
|
|
1570
|
+
// Simulate a retry with escalated strategy
|
|
1571
|
+
console.log('\n=== Attempt 2 (retry with different strategy) ===')
|
|
1572
|
+
const fixedCodeRetry = await fixCodeSample(
|
|
1573
|
+
client,
|
|
1574
|
+
brokenCode,
|
|
1575
|
+
errorMessage,
|
|
1576
|
+
contextDescription,
|
|
1577
|
+
2 // second attempt — broader rewrite
|
|
1578
|
+
)
|
|
1579
|
+
console.log('\nFixed code (retry):\n')
|
|
1580
|
+
console.log(fixedCodeRetry)
|
|
1581
|
+
|
|
1582
|
+
// Expected output:
|
|
1583
|
+
// A corrected version of fetchUser with proper `await response.json()`
|
|
1584
|
+
// and error handling, ready to replace the broken sample.
|
|
1585
|
+
} catch (error) {
|
|
1586
|
+
console.error('fixCodeSample failed:', error)
|
|
1587
|
+
process.exit(1)
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
main()
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
---
|
|
1595
|
+
|
|
1596
|
+
|
|
1597
|
+
## `generateDocumentation`
|
|
1598
|
+
|
|
1599
|
+
```typescript
|
|
1600
|
+
async function generateDocumentation(client: LLMClient, element: ElementContext, options?: { multiLanguage?: boolean }): Promise<GeneratedDocResult>
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
Use this to automatically generate documentation for a code element (function, class, method, etc.) using an LLM client, with optional multi-language support.
|
|
1604
|
+
|
|
1605
|
+
This is the primary entry point for producing structured documentation output from parsed code context. Pass in your configured LLM client and a description of the code element, and receive a ready-to-use documentation result.
|
|
1606
|
+
|
|
1607
|
+
## Parameters
|
|
1608
|
+
|
|
1609
|
+
| Name | Type | Required | Description |
|
|
1610
|
+
|------|------|----------|-------------|
|
|
1611
|
+
| `client` | `LLMClient` | ✅ Yes | A configured LLM client instance used to generate the documentation |
|
|
1612
|
+
| `element` | `ElementContext` | ✅ Yes | Context object describing the code element to document (name, signature, source, etc.) |
|
|
1613
|
+
| `options` | `{ multiLanguage?: boolean }` | ❌ No | Optional settings. `multiLanguage` defaults to `true` — when enabled, generates code examples in multiple languages |
|
|
1614
|
+
|
|
1615
|
+
## Returns
|
|
1616
|
+
|
|
1617
|
+
Returns a `Promise<GeneratedDocResult>` that resolves to a structured documentation object. The exact shape depends on the element type, but typically includes:
|
|
1618
|
+
|
|
1619
|
+
| Field | Description |
|
|
1620
|
+
|-------|-------------|
|
|
1621
|
+
| `markdown` | The generated documentation in Markdown format |
|
|
1622
|
+
| `code` | A self-contained code example |
|
|
1623
|
+
| `summary` | A short plain-text summary of the element |
|
|
1624
|
+
|
|
1625
|
+
Rejects with an error if the LLM client fails or the element context is malformed.
|
|
1626
|
+
|
|
1627
|
+
### Example
|
|
1628
|
+
|
|
1629
|
+
```typescript example.ts
|
|
1630
|
+
// ─── Inline type definitions (no external imports needed) ───────────────────
|
|
1631
|
+
|
|
1632
|
+
type LLMMessage = { role: 'user' | 'assistant' | 'system'; content: string }
|
|
1633
|
+
|
|
1634
|
+
interface LLMClient {
|
|
1635
|
+
complete(messages: LLMMessage[]): Promise<string>
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
interface ElementContext {
|
|
1639
|
+
name: string
|
|
1640
|
+
signature: string
|
|
1641
|
+
language: string
|
|
1642
|
+
sourceCode: string
|
|
1643
|
+
docstring?: string
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
interface GeneratedDocResult {
|
|
1647
|
+
markdown: string
|
|
1648
|
+
code: string
|
|
1649
|
+
summary: string
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// ─── Simulated generateDocumentation (mirrors real behavior) ─────────────────
|
|
1653
|
+
|
|
1654
|
+
async function generateDocumentation(
|
|
1655
|
+
client: LLMClient,
|
|
1656
|
+
element: ElementContext,
|
|
1657
|
+
options?: { multiLanguage?: boolean }
|
|
1658
|
+
): Promise<GeneratedDocResult> {
|
|
1659
|
+
const useMultiLang = options?.multiLanguage ?? true
|
|
1660
|
+
|
|
1661
|
+
const prompt = [
|
|
1662
|
+
`Generate documentation for the following ${element.language} code element.`,
|
|
1663
|
+
`Name: ${element.name}`,
|
|
1664
|
+
`Signature: ${element.signature}`,
|
|
1665
|
+
useMultiLang
|
|
1666
|
+
? 'Include code examples in multiple languages (TypeScript, Python, Go).'
|
|
1667
|
+
: 'Include a code example in the original language only.',
|
|
1668
|
+
'',
|
|
1669
|
+
'Source:',
|
|
1670
|
+
element.sourceCode,
|
|
1671
|
+
element.docstring ? `\nExisting docstring: ${element.docstring}` : '',
|
|
1672
|
+
].join('\n')
|
|
1673
|
+
|
|
1674
|
+
const raw = await client.complete([{ role: 'user', content: prompt }])
|
|
1675
|
+
|
|
1676
|
+
// In the real implementation, the response is parsed into structured fields.
|
|
1677
|
+
// Here we simulate that parsing step.
|
|
1678
|
+
return {
|
|
1679
|
+
markdown: `## \`${element.name}\`\n\n${raw}`,
|
|
1680
|
+
code: `// Example usage of ${element.name}\nconst result = await ${element.name}(client, element)`,
|
|
1681
|
+
summary: `Generates documentation for \`${element.name}\` using an LLM.`,
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// ─── Simulated LLM client (replace with a real client in production) ──────────
|
|
1686
|
+
|
|
1687
|
+
function createMockLLMClient(apiKey: string): LLMClient {
|
|
1688
|
+
console.log(`LLM client initialized (key: ${apiKey.slice(0, 8)}...)`)
|
|
1689
|
+
return {
|
|
1690
|
+
async complete(messages: LLMMessage[]): Promise<string> {
|
|
1691
|
+
// Simulate an LLM response
|
|
1692
|
+
const userMessage = messages.find((m) => m.role === 'user')?.content ?? ''
|
|
1693
|
+
return [
|
|
1694
|
+
'Use this to calculate the total price including tax.',
|
|
1695
|
+
'',
|
|
1696
|
+
'**Parameters:** `price: number`, `taxRate: number`',
|
|
1697
|
+
'**Returns:** `number` — the final price after tax is applied.',
|
|
1698
|
+
'',
|
|
1699
|
+
userMessage.includes('multiple languages')
|
|
1700
|
+
? '```typescript\nconst total = calculateTotal(100, 0.08) // 108\n```'
|
|
1701
|
+
: '```python\ntotal = calculate_total(100, 0.08) # 108.0\n```',
|
|
1702
|
+
].join('\n')
|
|
1703
|
+
},
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// ─── Main usage example ───────────────────────────────────────────────────────
|
|
1708
|
+
|
|
1709
|
+
async function main() {
|
|
1710
|
+
const apiKey = process.env.OPENAI_API_KEY || 'sk-your-api-key-here'
|
|
1711
|
+
const client = createMockLLMClient(apiKey)
|
|
1712
|
+
|
|
1713
|
+
const element: ElementContext = {
|
|
1714
|
+
name: 'calculateTotal',
|
|
1715
|
+
signature: 'function calculateTotal(price: number, taxRate: number): number',
|
|
1716
|
+
language: 'TypeScript',
|
|
1717
|
+
sourceCode: `
|
|
1718
|
+
function calculateTotal(price: number, taxRate: number): number {
|
|
1719
|
+
return price + price * taxRate
|
|
1720
|
+
}`.trim(),
|
|
1721
|
+
docstring: 'Calculates the total price after applying tax.',
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
try {
|
|
1725
|
+
// Generate docs with multi-language examples (default behavior)
|
|
1726
|
+
const result = await generateDocumentation(client, element, {
|
|
1727
|
+
multiLanguage: true,
|
|
1728
|
+
})
|
|
1729
|
+
|
|
1730
|
+
console.log('=== Generated Documentation ===')
|
|
1731
|
+
console.log('\n[Markdown]\n', result.markdown)
|
|
1732
|
+
console.log('\n[Code Example]\n', result.code)
|
|
1733
|
+
console.log('\n[Summary]\n', result.summary)
|
|
1734
|
+
|
|
1735
|
+
// Generate docs with single-language example
|
|
1736
|
+
const singleLang = await generateDocumentation(client, element, {
|
|
1737
|
+
multiLanguage: false,
|
|
1738
|
+
})
|
|
1739
|
+
console.log('\n=== Single-Language Mode ===')
|
|
1740
|
+
console.log('[Markdown]\n', singleLang.markdown)
|
|
1741
|
+
|
|
1742
|
+
// Expected output shape:
|
|
1743
|
+
// {
|
|
1744
|
+
// markdown: "## `calculateTotal`\n\nUse this to calculate...",
|
|
1745
|
+
// code: "// Example usage of calculateTotal\nconst result = ...",
|
|
1746
|
+
// summary: "Generates documentation for `calculateTotal` using an LLM."
|
|
1747
|
+
// }
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
if (error instanceof Error) {
|
|
1750
|
+
console.error('Documentation generation failed:', error.message)
|
|
1751
|
+
} else {
|
|
1752
|
+
console.error('Unexpected error:', error)
|
|
1753
|
+
}
|
|
1754
|
+
process.exit(1)
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
main()
|
|
1759
|
+
```
|
|
1760
|
+
|
|
1761
|
+
---
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
## `getAuthConfig`
|
|
1765
|
+
|
|
1766
|
+
```typescript
|
|
1767
|
+
function getAuthConfig(): AuthConfig
|
|
1768
|
+
```
|
|
1769
|
+
|
|
1770
|
+
Use this to synchronously retrieve the current authentication configuration from environment variables or a local auth file — without touching the keychain.
|
|
1771
|
+
|
|
1772
|
+
This is the fast, synchronous option for reading auth state. It checks `SKRYPT_API_KEY` env var first, then falls back to a local auth file. When you need keychain access (e.g., in interactive CLI flows), use `getAuthConfigAsync()` instead.
|
|
1773
|
+
|
|
1774
|
+
**Common use cases:**
|
|
1775
|
+
- Reading auth config at startup in a script or server process
|
|
1776
|
+
- Checking if a user is authenticated before making API calls
|
|
1777
|
+
- CI/CD environments where the API key is injected via environment variable
|
|
1778
|
+
|
|
1779
|
+
## Parameters
|
|
1780
|
+
|
|
1781
|
+
This function takes no parameters.
|
|
1782
|
+
|
|
1783
|
+
## Returns
|
|
1784
|
+
|
|
1785
|
+
Returns an `AuthConfig` object:
|
|
1786
|
+
|
|
1787
|
+
| Field | Type | Always Present | Description |
|
|
1788
|
+
|-------|------|----------------|-------------|
|
|
1789
|
+
| `apiKey` | `string \| undefined` | No | The API key, sourced from `SKRYPT_API_KEY` env var or auth file |
|
|
1790
|
+
| `email` | `string \| undefined` | No | The user's email address, sourced from the auth file |
|
|
1791
|
+
|
|
1792
|
+
Returns an empty/partial `AuthConfig` (with `undefined` fields) if no credentials are found — it does **not** throw.
|
|
1793
|
+
|
|
1794
|
+
## Source Priority
|
|
1795
|
+
|
|
1796
|
+
| Priority | Source | Notes |
|
|
1797
|
+
|----------|--------|-------|
|
|
1798
|
+
| 1st | `SKRYPT_API_KEY` env var | Always checked first |
|
|
1799
|
+
| 2nd | Local auth file | `~/.skrypt/auth.json` or similar |
|
|
1800
|
+
| ❌ | Keychain | Never accessed — use `getAuthConfigAsync()` for this |
|
|
1801
|
+
|
|
1802
|
+
### Example
|
|
1803
|
+
|
|
1804
|
+
```typescript example.ts
|
|
1805
|
+
// Inline types — no external imports needed
|
|
1806
|
+
type AuthConfig = {
|
|
1807
|
+
apiKey: string | undefined
|
|
1808
|
+
email: string | undefined
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// Simulate the auth file structure stored on disk
|
|
1812
|
+
type AuthFileMeta = {
|
|
1813
|
+
apiKey?: string
|
|
1814
|
+
email?: string
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// Simulate reading a local auth file (e.g., ~/.skrypt/auth.json)
|
|
1818
|
+
function readAuthFile(): AuthFileMeta {
|
|
1819
|
+
// In real usage, this reads from the filesystem
|
|
1820
|
+
// Here we simulate a stored auth file with example data
|
|
1821
|
+
return {
|
|
1822
|
+
apiKey: 'sk_file_abc123xyz',
|
|
1823
|
+
email: 'developer@example.com',
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// Self-contained implementation of getAuthConfig
|
|
1828
|
+
function getAuthConfig(): AuthConfig {
|
|
1829
|
+
// Priority 1: environment variable
|
|
1830
|
+
if (process.env.SKRYPT_API_KEY) {
|
|
1831
|
+
const fileMeta = readAuthFile()
|
|
1832
|
+
return {
|
|
1833
|
+
apiKey: process.env.SKRYPT_API_KEY,
|
|
1834
|
+
email: fileMeta.email,
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// Priority 2: local auth file
|
|
1839
|
+
const fileMeta = readAuthFile()
|
|
1840
|
+
if (fileMeta.apiKey) {
|
|
1841
|
+
return {
|
|
1842
|
+
apiKey: fileMeta.apiKey,
|
|
1843
|
+
email: fileMeta.email,
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// No credentials found — return empty config (does not throw)
|
|
1848
|
+
return {
|
|
1849
|
+
apiKey: undefined,
|
|
1850
|
+
email: undefined,
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// --- Usage ---
|
|
1855
|
+
|
|
1856
|
+
// Scenario 1: API key set via environment variable (common in CI/CD)
|
|
1857
|
+
process.env.SKRYPT_API_KEY = process.env.SKRYPT_API_KEY || 'sk_live_9f8e7d6c5b4a'
|
|
1858
|
+
|
|
1859
|
+
const config = getAuthConfig()
|
|
1860
|
+
|
|
1861
|
+
if (!config.apiKey) {
|
|
1862
|
+
console.error('Not authenticated. Run `skrypt login` or set SKRYPT_API_KEY.')
|
|
1863
|
+
process.exit(1)
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
console.log('Auth config loaded:')
|
|
1867
|
+
console.log(` API Key: ${config.apiKey}`)
|
|
1868
|
+
console.log(` Email: ${config.email ?? '(not set)'}`)
|
|
1869
|
+
// Output:
|
|
1870
|
+
// Auth config loaded:
|
|
1871
|
+
// API Key: sk_live_9f8e7d6c5b4a
|
|
1872
|
+
// Email: developer@example.com
|
|
1873
|
+
|
|
1874
|
+
// Scenario 2: No env var — falls back to auth file
|
|
1875
|
+
delete process.env.SKRYPT_API_KEY
|
|
1876
|
+
|
|
1877
|
+
const configFromFile = getAuthConfig()
|
|
1878
|
+
console.log('\nFallback to auth file:')
|
|
1879
|
+
console.log(` API Key: ${configFromFile.apiKey}`)
|
|
1880
|
+
console.log(` Email: ${configFromFile.email}`)
|
|
1881
|
+
// Output:
|
|
1882
|
+
// Fallback to auth file:
|
|
1883
|
+
// API Key: sk_file_abc123xyz
|
|
1884
|
+
// Email: developer@example.com
|
|
1885
|
+
|
|
1886
|
+
// Scenario 3: No credentials anywhere
|
|
1887
|
+
function getAuthConfigEmpty(): AuthConfig {
|
|
1888
|
+
return { apiKey: undefined, email: undefined }
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
const empty = getAuthConfigEmpty()
|
|
1892
|
+
console.log('\nNo credentials found:', empty.apiKey ?? 'unauthenticated')
|
|
1893
|
+
// Output:
|
|
1894
|
+
// No credentials found: unauthenticated
|
|
1895
|
+
```
|
|
1896
|
+
|
|
1897
|
+
### Related
|
|
1898
|
+
|
|
1899
|
+
<CardGroup cols={3}>
|
|
1900
|
+
<Card title="getAuthConfigAsync" icon="link" href="#getauthconfigasync">
|
|
1901
|
+
Uses
|
|
1902
|
+
</Card>
|
|
1903
|
+
<Card title="getAuthConfigAsync" icon="link" href="#getauthconfigasync">
|
|
1904
|
+
Used by
|
|
1905
|
+
</Card>
|
|
1906
|
+
<Card title="requirePro" icon="link" href="#requirepro">
|
|
1907
|
+
Used by
|
|
1908
|
+
</Card>
|
|
1909
|
+
</CardGroup>
|
|
1910
|
+
|
|
1911
|
+
---
|
|
1912
|
+
|
|
1913
|
+
|
|
1914
|
+
## `getAuthConfigAsync`
|
|
1915
|
+
|
|
1916
|
+
```typescript
|
|
1917
|
+
async function getAuthConfigAsync(): Promise<AuthConfig>
|
|
1918
|
+
```
|
|
1919
|
+
|
|
1920
|
+
Use this to retrieve the current user's authentication configuration in any CLI command action. It automatically resolves credentials by checking three sources in priority order: environment variable → system keychain → auth file on disk.
|
|
1921
|
+
|
|
1922
|
+
This is the **preferred method** for reading auth in all command handlers — it handles all credential sources transparently so you don't need to check each one manually.
|
|
1923
|
+
|
|
1924
|
+
## Returns
|
|
1925
|
+
|
|
1926
|
+
| Condition | Result |
|
|
1927
|
+
|---|---|
|
|
1928
|
+
| `SKRYPT_API_KEY` env var is set | Returns `AuthConfig` with API key from env + email from auth file |
|
|
1929
|
+
| Env var not set, keychain has credentials | Returns `AuthConfig` from keychain |
|
|
1930
|
+
| Neither env nor keychain, auth file exists | Returns `AuthConfig` read from auth file |
|
|
1931
|
+
| No credentials found anywhere | Throws or returns empty/null config |
|
|
1932
|
+
|
|
1933
|
+
## AuthConfig Shape
|
|
1934
|
+
|
|
1935
|
+
| Field | Type | Description |
|
|
1936
|
+
|---|---|---|
|
|
1937
|
+
| `apiKey` | `string` | The API key used to authenticate requests |
|
|
1938
|
+
| `email` | `string \| undefined` | The user's email address, if stored |
|
|
1939
|
+
|
|
1940
|
+
## Resolution Priority
|
|
1941
|
+
|
|
1942
|
+
```text
|
|
1943
|
+
SKRYPT_API_KEY (env var)
|
|
1944
|
+
↓ not found
|
|
1945
|
+
System Keychain
|
|
1946
|
+
↓ not found
|
|
1947
|
+
~/.config/skrypt/auth (file)
|
|
1948
|
+
```
|
|
1949
|
+
|
|
1950
|
+
### Example
|
|
1951
|
+
|
|
1952
|
+
```typescript example.ts
|
|
1953
|
+
// Inline types — no external imports needed
|
|
1954
|
+
type AuthConfig = {
|
|
1955
|
+
apiKey: string
|
|
1956
|
+
email?: string
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
type AuthFileMeta = {
|
|
1960
|
+
email?: string
|
|
1961
|
+
apiKey?: string
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// --- Simulated dependencies (stand-ins for fs, keychain, etc.) ---
|
|
1965
|
+
|
|
1966
|
+
const mockAuthFile: AuthFileMeta = {
|
|
1967
|
+
email: 'jane@example.com',
|
|
1968
|
+
apiKey: 'file-key-abc123',
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
const mockKeychain: AuthFileMeta = {
|
|
1972
|
+
email: 'jane@example.com',
|
|
1973
|
+
apiKey: 'keychain-key-xyz789',
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
function readAuthFile(): AuthFileMeta {
|
|
1977
|
+
// In real usage: reads from ~/.config/skrypt/auth
|
|
1978
|
+
return mockAuthFile
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
async function keychainRetrieve(): Promise<AuthFileMeta | null> {
|
|
1982
|
+
// In real usage: reads from OS keychain (macOS Keychain, etc.)
|
|
1983
|
+
return mockKeychain
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// --- Self-contained implementation of getAuthConfigAsync ---
|
|
1987
|
+
|
|
1988
|
+
async function getAuthConfigAsync(): Promise<AuthConfig> {
|
|
1989
|
+
// Priority 1: Environment variable
|
|
1990
|
+
if (process.env.SKRYPT_API_KEY) {
|
|
1991
|
+
const fileMeta = readAuthFile()
|
|
1992
|
+
return {
|
|
1993
|
+
apiKey: process.env.SKRYPT_API_KEY,
|
|
1994
|
+
email: fileMeta.email,
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// Priority 2: System keychain
|
|
1999
|
+
const keychainMeta = await keychainRetrieve()
|
|
2000
|
+
if (keychainMeta?.apiKey) {
|
|
2001
|
+
return {
|
|
2002
|
+
apiKey: keychainMeta.apiKey,
|
|
2003
|
+
email: keychainMeta.email,
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// Priority 3: Auth file on disk
|
|
2008
|
+
const fileMeta = readAuthFile()
|
|
2009
|
+
if (fileMeta?.apiKey) {
|
|
2010
|
+
return {
|
|
2011
|
+
apiKey: fileMeta.apiKey,
|
|
2012
|
+
email: fileMeta.email,
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
throw new Error(
|
|
2017
|
+
'No credentials found. Run `skrypt login` or set SKRYPT_API_KEY.'
|
|
2018
|
+
)
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// --- Usage example ---
|
|
2022
|
+
|
|
2023
|
+
async function main() {
|
|
2024
|
+
try {
|
|
2025
|
+
// Scenario A: env var is set (highest priority)
|
|
2026
|
+
process.env.SKRYPT_API_KEY = process.env.SKRYPT_API_KEY || 'env-key-sk_live_9f3kd82'
|
|
2027
|
+
|
|
2028
|
+
const authConfig = await getAuthConfigAsync()
|
|
2029
|
+
|
|
2030
|
+
console.log('Resolved auth config:')
|
|
2031
|
+
console.log(' API Key:', authConfig.apiKey)
|
|
2032
|
+
console.log(' Email: ', authConfig.email ?? '(not stored)')
|
|
2033
|
+
// Output:
|
|
2034
|
+
// API Key: env-key-sk_live_9f3kd82
|
|
2035
|
+
// Email: jane@example.com
|
|
2036
|
+
|
|
2037
|
+
// Scenario B: no env var — falls through to keychain
|
|
2038
|
+
delete process.env.SKRYPT_API_KEY
|
|
2039
|
+
const authFromKeychain = await getAuthConfigAsync()
|
|
2040
|
+
console.log('\nWithout env var (keychain fallback):')
|
|
2041
|
+
console.log(' API Key:', authFromKeychain.apiKey)
|
|
2042
|
+
// Output:
|
|
2043
|
+
// API Key: keychain-key-xyz789
|
|
2044
|
+
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
if (error instanceof Error) {
|
|
2047
|
+
console.error('Auth error:', error.message)
|
|
2048
|
+
// Handle: prompt user to run `skrypt login`
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
main()
|
|
2054
|
+
```
|
|
2055
|
+
|
|
2056
|
+
### Related
|
|
2057
|
+
|
|
2058
|
+
<CardGroup cols={3}>
|
|
2059
|
+
<Card title="getAuthConfig" icon="link" href="#getauthconfig">
|
|
2060
|
+
Uses
|
|
2061
|
+
</Card>
|
|
2062
|
+
<Card title="getAuthConfig" icon="link" href="#getauthconfig">
|
|
2063
|
+
Used by
|
|
2064
|
+
</Card>
|
|
2065
|
+
<Card title="requirePro" icon="link" href="#requirepro">
|
|
2066
|
+
Used by
|
|
2067
|
+
</Card>
|
|
2068
|
+
</CardGroup>
|
|
2069
|
+
|
|
2070
|
+
---
|
|
2071
|
+
|
|
2072
|
+
|
|
2073
|
+
## `getKeyStorageMethod`
|
|
2074
|
+
|
|
2075
|
+
```typescript
|
|
2076
|
+
async function getKeyStorageMethod(): Promise<'keychain' | 'file' | 'env' | 'none'>
|
|
2077
|
+
```
|
|
2078
|
+
|
|
2079
|
+
Use this to detect where an API key is currently stored, so you can display storage status to users, conditionally prompt for re-authentication, or branch logic based on the active credential source.
|
|
2080
|
+
|
|
2081
|
+
Returns a promise resolving to one of four string literals indicating the active storage method — checked in priority order: environment variable → keychain → file → none.
|
|
2082
|
+
|
|
2083
|
+
### Return Values
|
|
2084
|
+
|
|
2085
|
+
| Value | Description |
|
|
2086
|
+
|---|---|
|
|
2087
|
+
| `'env'` | API key found in `SKRYPT_API_KEY` environment variable |
|
|
2088
|
+
| `'keychain'` | API key stored in the OS keychain (macOS/Linux/Windows credential store) |
|
|
2089
|
+
| `'file'` | API key stored in a local auth file on disk |
|
|
2090
|
+
| `'none'` | No API key found in any storage location |
|
|
2091
|
+
|
|
2092
|
+
### Parameters
|
|
2093
|
+
|
|
2094
|
+
This function takes no parameters.
|
|
2095
|
+
|
|
2096
|
+
### Notes
|
|
2097
|
+
- Checks are performed in priority order — if `SKRYPT_API_KEY` is set in the environment, it returns `'env'` immediately without checking other sources.
|
|
2098
|
+
- Use the result to give users meaningful feedback (e.g. *"Logged in via keychain"*) or to decide whether to prompt for a new key.
|
|
2099
|
+
|
|
2100
|
+
### Example
|
|
2101
|
+
|
|
2102
|
+
```typescript example.ts
|
|
2103
|
+
// Simulate the storage detection logic inline
|
|
2104
|
+
// (mirrors the real priority order: env → keychain → file → none)
|
|
2105
|
+
|
|
2106
|
+
import { existsSync } from 'fs'
|
|
2107
|
+
import { join } from 'path'
|
|
2108
|
+
import { homedir } from 'os'
|
|
2109
|
+
|
|
2110
|
+
type KeyStorageMethod = 'keychain' | 'file' | 'env' | 'none'
|
|
2111
|
+
|
|
2112
|
+
// Simulate keychain lookup (real impl queries OS credential store)
|
|
2113
|
+
async function simulateKeychainRetrieve(): Promise<string | null> {
|
|
2114
|
+
// In production, this calls the OS keychain API
|
|
2115
|
+
// Here we simulate "no keychain entry found"
|
|
2116
|
+
return null
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
// Simulate file-based auth check
|
|
2120
|
+
function simulateFileKeyExists(): boolean {
|
|
2121
|
+
const AUTH_FILE = join(homedir(), '.skrypt', 'auth.json')
|
|
2122
|
+
return existsSync(AUTH_FILE)
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
async function getKeyStorageMethod(): Promise<KeyStorageMethod> {
|
|
2126
|
+
// Priority 1: environment variable
|
|
2127
|
+
if (process.env.SKRYPT_API_KEY) return 'env'
|
|
2128
|
+
|
|
2129
|
+
// Priority 2: OS keychain
|
|
2130
|
+
const keychainKey = await simulateKeychainRetrieve()
|
|
2131
|
+
if (keychainKey) return 'keychain'
|
|
2132
|
+
|
|
2133
|
+
// Priority 3: local auth file
|
|
2134
|
+
if (simulateFileKeyExists()) return 'file'
|
|
2135
|
+
|
|
2136
|
+
// Priority 4: not found anywhere
|
|
2137
|
+
return 'none'
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
async function main() {
|
|
2141
|
+
try {
|
|
2142
|
+
const method = await getKeyStorageMethod()
|
|
2143
|
+
|
|
2144
|
+
console.log('Storage method detected:', method)
|
|
2145
|
+
// Output (no key set): Storage method detected: none
|
|
2146
|
+
|
|
2147
|
+
// Example: branch on result to show user-facing status
|
|
2148
|
+
const statusMessages: Record<KeyStorageMethod, string> = {
|
|
2149
|
+
env: '🔑 Using API key from environment variable (SKRYPT_API_KEY)',
|
|
2150
|
+
keychain: '🔐 Using API key stored in OS keychain',
|
|
2151
|
+
file: '📄 Using API key stored in local auth file (~/.skrypt/auth.json)',
|
|
2152
|
+
none: '⚠️ No API key found — please run `skrypt login`',
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
console.log(statusMessages[method])
|
|
2156
|
+
// Output: ⚠️ No API key found — please run `skrypt login`
|
|
2157
|
+
|
|
2158
|
+
// Example: simulate env variable being set
|
|
2159
|
+
process.env.SKRYPT_API_KEY = 'sk-prod-abc123xyz'
|
|
2160
|
+
const methodWithEnv = await getKeyStorageMethod()
|
|
2161
|
+
console.log('With env var set:', methodWithEnv)
|
|
2162
|
+
// Output: With env var set: env
|
|
2163
|
+
|
|
2164
|
+
} catch (error) {
|
|
2165
|
+
console.error('Failed to detect key storage method:', error)
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
main()
|
|
2170
|
+
```
|
|
2171
|
+
|
|
2172
|
+
### Related
|
|
2173
|
+
|
|
2174
|
+
<CardGroup cols={3}>
|
|
2175
|
+
<Card title="keychainRetrieve" icon="link" href="#keychainretrieve">
|
|
2176
|
+
Uses
|
|
2177
|
+
</Card>
|
|
2178
|
+
</CardGroup>
|
|
2179
|
+
|
|
2180
|
+
---
|
|
2181
|
+
|
|
2182
|
+
|
|
2183
|
+
## `requirePro`
|
|
2184
|
+
|
|
2185
|
+
```typescript
|
|
2186
|
+
async function requirePro(commandName: string): Promise<boolean>
|
|
2187
|
+
```
|
|
2188
|
+
|
|
2189
|
+
Use this to gate CLI commands behind a Pro subscription, automatically printing upgrade instructions and returning `false` if the user isn't authenticated or doesn't have a Pro plan.
|
|
2190
|
+
|
|
2191
|
+
Call `requirePro` at the top of any command handler that requires a paid plan. It handles all messaging to the user — you just check the return value and exit early if it's `false`.
|
|
2192
|
+
|
|
2193
|
+
## Parameters
|
|
2194
|
+
|
|
2195
|
+
| Name | Type | Required | Description |
|
|
2196
|
+
|------|------|----------|-------------|
|
|
2197
|
+
| `commandName` | `string` | ✅ | The name of the CLI command being gated. Shown in the error message so users know which command requires Pro. |
|
|
2198
|
+
|
|
2199
|
+
## Returns
|
|
2200
|
+
|
|
2201
|
+
| Value | When |
|
|
2202
|
+
|-------|------|
|
|
2203
|
+
| `Promise<true>` | User is authenticated and has an active Pro plan — safe to proceed |
|
|
2204
|
+
| `Promise<false>` | User has no API key, is on the free plan, or the auth check failed — command should abort |
|
|
2205
|
+
|
|
2206
|
+
## Behavior
|
|
2207
|
+
|
|
2208
|
+
- Reads the stored auth config (API key + plan info)
|
|
2209
|
+
- If no API key is found → prints upgrade prompt and returns `false`
|
|
2210
|
+
- If plan is `free` or invalid → prints upgrade prompt and returns `false`
|
|
2211
|
+
- If Pro is confirmed → returns `true` silently, letting the command continue
|
|
2212
|
+
|
|
2213
|
+
### Example
|
|
2214
|
+
|
|
2215
|
+
```typescript example.ts
|
|
2216
|
+
// Inline types — no external imports needed
|
|
2217
|
+
type Plan = 'free' | 'pro' | 'enterprise'
|
|
2218
|
+
|
|
2219
|
+
interface AuthConfig {
|
|
2220
|
+
apiKey: string | null
|
|
2221
|
+
plan: Plan
|
|
2222
|
+
email: string
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
// Simulate stored auth config (in real usage, read from disk/keychain)
|
|
2226
|
+
const mockAuthStore: AuthConfig = {
|
|
2227
|
+
apiKey: process.env.SKRYPT_API_KEY || null,
|
|
2228
|
+
plan: 'free',
|
|
2229
|
+
email: 'user@example.com',
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
async function getAuthConfigAsync(): Promise<AuthConfig> {
|
|
2233
|
+
// Simulate async config read (e.g., from ~/.config or keychain)
|
|
2234
|
+
return new Promise((resolve) => setTimeout(() => resolve(mockAuthStore), 50))
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
async function requirePro(commandName: string): Promise<boolean> {
|
|
2238
|
+
const config = await getAuthConfigAsync()
|
|
2239
|
+
|
|
2240
|
+
if (!config.apiKey) {
|
|
2241
|
+
console.error(`\n ⚡ ${commandName} requires Skrypt Pro\n`)
|
|
2242
|
+
console.error(' Get started:')
|
|
2243
|
+
console.error(' https://skrypt.dev/pro\n')
|
|
2244
|
+
return false
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
if (config.plan !== 'pro' && config.plan !== 'enterprise') {
|
|
2248
|
+
console.error(`\n ⚡ ${commandName} requires Skrypt Pro\n`)
|
|
2249
|
+
console.error(` You're currently on the ${config.plan} plan.`)
|
|
2250
|
+
console.error(' Upgrade at: https://skrypt.dev/pro\n')
|
|
2251
|
+
return false
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
return true
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// --- Example usage in a CLI command handler ---
|
|
2258
|
+
|
|
2259
|
+
async function runExportCommand() {
|
|
2260
|
+
try {
|
|
2261
|
+
const allowed = await requirePro('export')
|
|
2262
|
+
|
|
2263
|
+
if (!allowed) {
|
|
2264
|
+
// requirePro already printed the error — just exit
|
|
2265
|
+
process.exitCode = 1
|
|
2266
|
+
return
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// Pro-only logic runs here
|
|
2270
|
+
console.log('✅ Pro confirmed — running export...')
|
|
2271
|
+
// Output: ⚡ export requires Skrypt Pro (when no API key)
|
|
2272
|
+
// Output: ✅ Pro confirmed — running export... (when Pro plan)
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
console.error('Unexpected error during auth check:', error)
|
|
2275
|
+
process.exitCode = 1
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
runExportCommand()
|
|
2280
|
+
```
|
|
2281
|
+
|
|
2282
|
+
### Related
|
|
2283
|
+
|
|
2284
|
+
<CardGroup cols={3}>
|
|
2285
|
+
<Card title="getAuthConfig" icon="link" href="#getauthconfig">
|
|
2286
|
+
Uses
|
|
2287
|
+
</Card>
|
|
2288
|
+
<Card title="getAuthConfigAsync" icon="link" href="#getauthconfigasync">
|
|
2289
|
+
Uses
|
|
2290
|
+
</Card>
|
|
2291
|
+
</CardGroup>
|
|
2292
|
+
|
|
2293
|
+
---
|
|
2294
|
+
|
|
2295
|
+
|
|
2296
|
+
## `runImport`
|
|
2297
|
+
|
|
2298
|
+
```typescript
|
|
2299
|
+
function runImport(dir: string, format: ImportFormat, name?: string): ImportResult
|
|
2300
|
+
```
|
|
2301
|
+
|
|
2302
|
+
Use this to automatically import and process a documentation directory based on its detected format (Mintlify, Docusaurus, GitBook, ReadMe, Confluence, or plain Markdown).
|
|
2303
|
+
|
|
2304
|
+
Instead of manually selecting the right importer, pass the directory path and detected format — `runImport` routes to the correct importer and returns a normalized result.
|
|
2305
|
+
|
|
2306
|
+
## Parameters
|
|
2307
|
+
|
|
2308
|
+
| Name | Type | Required | Description |
|
|
2309
|
+
|------|------|----------|-------------|
|
|
2310
|
+
| `dir` | `string` | ✅ Yes | Path to the documentation directory to import |
|
|
2311
|
+
| `format` | `ImportFormat` | ✅ Yes | The detected format of the docs (`'mintlify'`, `'docusaurus'`, `'gitbook'`, `'readme'`, `'confluence'`, `'markdown'`) |
|
|
2312
|
+
| `name` | `string` | ❌ No | Optional display name for the imported documentation set |
|
|
2313
|
+
|
|
2314
|
+
## Returns
|
|
2315
|
+
|
|
2316
|
+
Returns an `ImportResult` object containing the processed documentation content, metadata, and any warnings or errors encountered during import.
|
|
2317
|
+
|
|
2318
|
+
| Scenario | Result |
|
|
2319
|
+
|----------|--------|
|
|
2320
|
+
| Successful import | `ImportResult` with parsed pages, metadata, and file tree |
|
|
2321
|
+
| Unrecognized format | Throws or falls through to default handler |
|
|
2322
|
+
| Invalid directory | Throws with a path-related error |
|
|
2323
|
+
|
|
2324
|
+
### Example
|
|
2325
|
+
|
|
2326
|
+
```typescript example.ts
|
|
2327
|
+
// --- Inline types (mirrors autodocs internals) ---
|
|
2328
|
+
type ImportFormat = 'mintlify' | 'docusaurus' | 'gitbook' | 'readme' | 'confluence' | 'markdown'
|
|
2329
|
+
|
|
2330
|
+
interface ImportedPage {
|
|
2331
|
+
path: string
|
|
2332
|
+
title: string
|
|
2333
|
+
content: string
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
interface ImportResult {
|
|
2337
|
+
name: string
|
|
2338
|
+
format: ImportFormat
|
|
2339
|
+
pages: ImportedPage[]
|
|
2340
|
+
warnings: string[]
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// --- Simulated per-format importers ---
|
|
2344
|
+
function importMintlify(dir: string, name?: string): ImportResult {
|
|
2345
|
+
return {
|
|
2346
|
+
name: name ?? 'Mintlify Docs',
|
|
2347
|
+
format: 'mintlify',
|
|
2348
|
+
pages: [{ path: `${dir}/index.mdx`, title: 'Introduction', content: '# Welcome to Mintlify' }],
|
|
2349
|
+
warnings: [],
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
function importDocusaurus(dir: string, name?: string): ImportResult {
|
|
2354
|
+
return {
|
|
2355
|
+
name: name ?? 'Docusaurus Docs',
|
|
2356
|
+
format: 'docusaurus',
|
|
2357
|
+
pages: [{ path: `${dir}/docs/intro.md`, title: 'Intro', content: '# Getting Started' }],
|
|
2358
|
+
warnings: ['Sidebar config not found, using default order'],
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
function importGitBook(dir: string, name?: string): ImportResult {
|
|
2363
|
+
return {
|
|
2364
|
+
name: name ?? 'GitBook Docs',
|
|
2365
|
+
format: 'gitbook',
|
|
2366
|
+
pages: [{ path: `${dir}/README.md`, title: 'Overview', content: '# Overview' }],
|
|
2367
|
+
warnings: [],
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function importReadme(dir: string, name?: string): ImportResult {
|
|
2372
|
+
return {
|
|
2373
|
+
name: name ?? 'ReadMe Docs',
|
|
2374
|
+
format: 'readme',
|
|
2375
|
+
pages: [{ path: `${dir}/v1.0/getting-started.md`, title: 'Getting Started', content: '# API Docs' }],
|
|
2376
|
+
warnings: [],
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function importConfluence(dir: string, name?: string): ImportResult {
|
|
2381
|
+
return {
|
|
2382
|
+
name: name ?? 'Confluence Export',
|
|
2383
|
+
format: 'confluence',
|
|
2384
|
+
pages: [{ path: `${dir}/space/page.html`, title: 'Home', content: '<h1>Confluence Page</h1>' }],
|
|
2385
|
+
warnings: ['HTML content detected — consider converting to Markdown'],
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
function importMarkdown(dir: string, name?: string): ImportResult {
|
|
2390
|
+
return {
|
|
2391
|
+
name: name ?? 'Markdown Docs',
|
|
2392
|
+
format: 'markdown',
|
|
2393
|
+
pages: [{ path: `${dir}/README.md`, title: 'README', content: '# My Project' }],
|
|
2394
|
+
warnings: [],
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
// --- The function under documentation ---
|
|
2399
|
+
function runImport(dir: string, format: ImportFormat, name?: string): ImportResult {
|
|
2400
|
+
switch (format) {
|
|
2401
|
+
case 'mintlify': return importMintlify(dir, name)
|
|
2402
|
+
case 'docusaurus': return importDocusaurus(dir, name)
|
|
2403
|
+
case 'gitbook': return importGitBook(dir, name)
|
|
2404
|
+
case 'readme': return importReadme(dir, name)
|
|
2405
|
+
case 'confluence': return importConfluence(dir, name)
|
|
2406
|
+
case 'markdown': return importMarkdown(dir, name)
|
|
2407
|
+
default:
|
|
2408
|
+
throw new Error(`Unsupported import format: ${format}`)
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// --- Usage examples ---
|
|
2413
|
+
async function main() {
|
|
2414
|
+
const docsDir = process.env.DOCS_DIR || './docs'
|
|
2415
|
+
|
|
2416
|
+
const formats: ImportFormat[] = ['mintlify', 'docusaurus', 'gitbook', 'readme', 'confluence', 'markdown']
|
|
2417
|
+
|
|
2418
|
+
for (const format of formats) {
|
|
2419
|
+
try {
|
|
2420
|
+
const result = runImport(docsDir, format, `My ${format} Project`)
|
|
2421
|
+
|
|
2422
|
+
console.log(`\n[${format.toUpperCase()}]`)
|
|
2423
|
+
console.log(` Name : ${result.name}`)
|
|
2424
|
+
console.log(` Pages : ${result.pages.length}`)
|
|
2425
|
+
console.log(` First : ${result.pages[0].title} → ${result.pages[0].path}`)
|
|
2426
|
+
if (result.warnings.length > 0) {
|
|
2427
|
+
console.log(` Warnings: ${result.warnings.join('; ')}`)
|
|
2428
|
+
}
|
|
2429
|
+
} catch (error) {
|
|
2430
|
+
console.error(`Failed to import [${format}]:`, error)
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// Expected output:
|
|
2435
|
+
// [MINTLIFY]
|
|
2436
|
+
// Name : My mintlify Project
|
|
2437
|
+
// Pages : 1
|
|
2438
|
+
// First : Introduction → ./docs/index.mdx
|
|
2439
|
+
//
|
|
2440
|
+
// [DOCUSAURUS]
|
|
2441
|
+
// Name : My docusaurus Project
|
|
2442
|
+
// Pages : 1
|
|
2443
|
+
// First : Intro → ./docs/docs/intro.md
|
|
2444
|
+
// Warnings: Sidebar config not found, using default order
|
|
2445
|
+
// ... (and so on for each format)
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
main()
|
|
2449
|
+
```
|
|
2450
|
+
|
|
2451
|
+
### Related
|
|
2452
|
+
|
|
2453
|
+
<CardGroup cols={3}>
|
|
2454
|
+
<Card title="importConfluence" icon="link" href="#importconfluence">
|
|
2455
|
+
Uses
|
|
2456
|
+
</Card>
|
|
2457
|
+
<Card title="importDocusaurus" icon="link" href="#importdocusaurus">
|
|
2458
|
+
Uses
|
|
2459
|
+
</Card>
|
|
2460
|
+
<Card title="importGitBook" icon="link" href="#importgitbook">
|
|
2461
|
+
Uses
|
|
2462
|
+
</Card>
|
|
2463
|
+
<Card title="importMintlify" icon="link" href="#importmintlify">
|
|
2464
|
+
Uses
|
|
2465
|
+
</Card>
|
|
2466
|
+
<Card title="importReadme" icon="link" href="#importreadme">
|
|
2467
|
+
Uses
|
|
2468
|
+
</Card>
|
|
2469
|
+
</CardGroup>
|
|
2470
|
+
|
|
2471
|
+
---
|
|
2472
|
+
|
|
2473
|
+
|
|
2474
|
+
## `saveAuthConfig`
|
|
2475
|
+
|
|
2476
|
+
```typescript
|
|
2477
|
+
async function saveAuthConfig(config: AuthConfig): Promise<void>
|
|
2478
|
+
```
|
|
2479
|
+
|
|
2480
|
+
Use this to persist authentication configuration (API keys, endpoints, tokens) to a secure local config directory, with automatic keychain integration when available.
|
|
2481
|
+
|
|
2482
|
+
This function:
|
|
2483
|
+
- Creates the config directory with restricted permissions (`0o700`) if it doesn't exist
|
|
2484
|
+
- Attempts to store the API key in the system keychain for added security
|
|
2485
|
+
- Falls back to writing credentials to a local config file if keychain storage is unavailable
|
|
2486
|
+
|
|
2487
|
+
## Parameters
|
|
2488
|
+
|
|
2489
|
+
| Name | Type | Required | Description |
|
|
2490
|
+
|------|------|----------|-------------|
|
|
2491
|
+
| `config` | `AuthConfig` | Yes | Authentication configuration object containing credentials to persist |
|
|
2492
|
+
| `config.apiKey` | `string` | No | API key to store — saved to system keychain when possible, otherwise written to config file |
|
|
2493
|
+
|
|
2494
|
+
## Returns
|
|
2495
|
+
|
|
2496
|
+
Returns `Promise<void>`. Resolves when the config has been successfully saved. Throws if the config directory cannot be created or the file cannot be written.
|
|
2497
|
+
|
|
2498
|
+
## Notes
|
|
2499
|
+
- The config directory is created with `0o700` permissions (owner read/write/execute only) — no other users can access it
|
|
2500
|
+
- If keychain storage succeeds, the API key is **not** written to disk in plaintext
|
|
2501
|
+
- Call `saveAuthConfig` any time credentials change (login, token refresh, API key rotation)
|
|
2502
|
+
|
|
2503
|
+
### Example
|
|
2504
|
+
|
|
2505
|
+
```typescript example.ts
|
|
2506
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from 'fs'
|
|
2507
|
+
import { join } from 'path'
|
|
2508
|
+
import { homedir } from 'os'
|
|
2509
|
+
|
|
2510
|
+
// --- Inline types ---
|
|
2511
|
+
interface AuthConfig {
|
|
2512
|
+
apiKey?: string
|
|
2513
|
+
endpoint?: string
|
|
2514
|
+
token?: string
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
// --- Inline config path constants ---
|
|
2518
|
+
const CONFIG_DIR = join(homedir(), '.config', 'myapp')
|
|
2519
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'auth.json')
|
|
2520
|
+
|
|
2521
|
+
// --- Simulate keychain (no real keychain in this example) ---
|
|
2522
|
+
async function keychainStore(apiKey: string): Promise<boolean> {
|
|
2523
|
+
// In production, this would call the OS keychain (e.g. macOS Keychain, libsecret)
|
|
2524
|
+
console.log(`[keychain] Attempted to store API key: ${apiKey.slice(0, 8)}...`)
|
|
2525
|
+
return false // Simulate keychain unavailable — falls back to file storage
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// --- Self-contained saveAuthConfig implementation ---
|
|
2529
|
+
async function saveAuthConfig(config: AuthConfig): Promise<void> {
|
|
2530
|
+
// Create config directory with restricted permissions (owner only)
|
|
2531
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })
|
|
2532
|
+
|
|
2533
|
+
let keyInKeychain = false
|
|
2534
|
+
if (config.apiKey) {
|
|
2535
|
+
keyInKeychain = await keychainStore(config.apiKey)
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// Build the object to persist — omit apiKey if it's safely in the keychain
|
|
2539
|
+
const persistedConfig: Partial<AuthConfig> & { apiKeyInKeychain?: boolean } = {
|
|
2540
|
+
endpoint: config.endpoint,
|
|
2541
|
+
token: config.token,
|
|
2542
|
+
apiKeyInKeychain: keyInKeychain,
|
|
2543
|
+
// Only write apiKey to disk if keychain storage failed
|
|
2544
|
+
...(!keyInKeychain && config.apiKey ? { apiKey: config.apiKey } : {}),
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(persistedConfig, null, 2), { mode: 0o600 })
|
|
2548
|
+
chmodSync(CONFIG_FILE, 0o600) // Ensure file is owner read/write only
|
|
2549
|
+
|
|
2550
|
+
console.log(`[saveAuthConfig] Config saved to: ${CONFIG_FILE}`)
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
// --- Usage example ---
|
|
2554
|
+
async function main() {
|
|
2555
|
+
const authConfig: AuthConfig = {
|
|
2556
|
+
apiKey: process.env.MY_API_KEY || 'sk-abc123def456ghi789',
|
|
2557
|
+
endpoint: process.env.API_ENDPOINT || 'https://api.example.com',
|
|
2558
|
+
token: process.env.AUTH_TOKEN || 'tok-xyz987',
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
try {
|
|
2562
|
+
await saveAuthConfig(authConfig)
|
|
2563
|
+
|
|
2564
|
+
// Verify what was written
|
|
2565
|
+
const saved = JSON.parse(readFileSync(join(homedir(), '.config', 'myapp', 'auth.json'), 'utf-8'))
|
|
2566
|
+
console.log('Saved config:', saved)
|
|
2567
|
+
// Output (keychain unavailable): {
|
|
2568
|
+
// endpoint: 'https://api.example.com',
|
|
2569
|
+
// token: 'tok-xyz987',
|
|
2570
|
+
// apiKeyInKeychain: false,
|
|
2571
|
+
// apiKey: 'sk-abc123def456ghi789'
|
|
2572
|
+
// }
|
|
2573
|
+
// Output (keychain available): {
|
|
2574
|
+
// endpoint: 'https://api.example.com',
|
|
2575
|
+
// token: 'tok-xyz987',
|
|
2576
|
+
// apiKeyInKeychain: true <-- apiKey NOT written to disk
|
|
2577
|
+
// }
|
|
2578
|
+
} catch (error) {
|
|
2579
|
+
console.error('Failed to save auth config:', error)
|
|
2580
|
+
process.exit(1)
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
main()
|
|
2585
|
+
```
|
|
2586
|
+
|
|
2587
|
+
### Related
|
|
2588
|
+
|
|
2589
|
+
<CardGroup cols={3}>
|
|
2590
|
+
<Card title="keychainStore" icon="link" href="#keychainstore">
|
|
2591
|
+
Uses
|
|
2592
|
+
</Card>
|
|
2593
|
+
</CardGroup>
|
|
2594
|
+
|
|
2595
|
+
---
|
|
2596
|
+
|
|
2597
|
+
|
|
2598
|
+
## `scanDirectory`
|
|
2599
|
+
|
|
2600
|
+
```typescript
|
|
2601
|
+
async function scanDirectory(dir: string, options: ScanOptions = {}): Promise<ScanAllResult>
|
|
2602
|
+
```
|
|
2603
|
+
|
|
2604
|
+
Use this to recursively scan a directory (or single file) for all API elements — functions, classes, types, and exports — across multiple languages including Python, TypeScript, JavaScript, Go, and Rust.
|
|
2605
|
+
|
|
2606
|
+
This is the primary entry point for automated documentation generation pipelines. Point it at a project root and get back a structured inventory of every public API element found.
|
|
2607
|
+
|
|
2608
|
+
## Parameters
|
|
2609
|
+
|
|
2610
|
+
| Name | Type | Required | Description |
|
|
2611
|
+
|------|------|----------|-------------|
|
|
2612
|
+
| `dir` | `string` | ✅ | Path to the directory or single file to scan |
|
|
2613
|
+
| `options` | `ScanOptions` | ❌ | Configuration to control which files are included or excluded |
|
|
2614
|
+
| `options.include` | `string[]` | ❌ | Glob patterns for files to scan. Defaults to `['**/*.py', '**/*.ts', '**/*.js', '**/*.go', '**/*.rs']` |
|
|
2615
|
+
| `options.exclude` | `string[]` | ❌ | Glob patterns for files to skip. Defaults to `['**/node_modules/**', '**/__pycache__/**', '**/dist/**']` |
|
|
2616
|
+
|
|
2617
|
+
## Returns
|
|
2618
|
+
|
|
2619
|
+
Returns a `Promise<ScanAllResult>` that resolves to an object containing:
|
|
2620
|
+
|
|
2621
|
+
| Field | Type | Description |
|
|
2622
|
+
|-------|------|-------------|
|
|
2623
|
+
| `files` | `ScanResult[]` | One entry per scanned file, each containing the file path and its discovered API elements |
|
|
2624
|
+
| `totalElements` | `number` | Total count of API elements found across all files |
|
|
2625
|
+
| `errors` | `ScanError[]` | Any files that failed to parse, with their error details |
|
|
2626
|
+
|
|
2627
|
+
Returns an empty `files` array if no matching files are found. Files that fail to parse are collected in `errors` rather than throwing, so a single bad file won't abort the entire scan.
|
|
2628
|
+
|
|
2629
|
+
---
|
|
2630
|
+
|
|
2631
|
+
|
|
2632
|
+
## `scanFile`
|
|
2633
|
+
|
|
2634
|
+
```typescript
|
|
2635
|
+
async function scanFile(filePath: string): Promise<ScanResult>
|
|
2636
|
+
```
|
|
2637
|
+
|
|
2638
|
+
Use this to extract structured metadata from a single source file — functions, classes, types, and other symbols — without scanning an entire directory.
|
|
2639
|
+
|
|
2640
|
+
`scanFile` inspects a file at the given path, selects the appropriate language scanner (TypeScript, JavaScript, Python, etc.), and returns a structured `ScanResult` containing all discovered symbols and language information.
|
|
2641
|
+
|
|
2642
|
+
## Parameters
|
|
2643
|
+
|
|
2644
|
+
| Name | Type | Required | Description |
|
|
2645
|
+
|------|------|----------|-------------|
|
|
2646
|
+
| `filePath` | `string` | ✅ | Absolute or relative path to the source file to scan |
|
|
2647
|
+
|
|
2648
|
+
## Returns
|
|
2649
|
+
|
|
2650
|
+
Returns `Promise<ScanResult>` — resolves with a structured object containing:
|
|
2651
|
+
|
|
2652
|
+
| Field | Type | Description |
|
|
2653
|
+
|-------|------|-------------|
|
|
2654
|
+
| `language` | `string` | Detected language (`'typescript'`, `'javascript'`, `'python'`, etc.) |
|
|
2655
|
+
| `symbols` | `Symbol[]` | Array of discovered functions, classes, types, and other declarations |
|
|
2656
|
+
| `filePath` | `string` | The resolved path of the scanned file |
|
|
2657
|
+
|
|
2658
|
+
**Throws** if the file does not exist, cannot be read, or has an unsupported extension with no registered scanner.
|
|
2659
|
+
|
|
2660
|
+
### Example
|
|
2661
|
+
|
|
2662
|
+
```typescript example.ts
|
|
2663
|
+
// Inline types matching the autodocs ScanResult shape
|
|
2664
|
+
type SymbolKind = 'function' | 'class' | 'type' | 'interface' | 'variable'
|
|
2665
|
+
|
|
2666
|
+
interface DocSymbol {
|
|
2667
|
+
name: string
|
|
2668
|
+
kind: SymbolKind
|
|
2669
|
+
line: number
|
|
2670
|
+
docstring?: string
|
|
2671
|
+
signature?: string
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
interface ScanResult {
|
|
2675
|
+
filePath: string
|
|
2676
|
+
language: 'typescript' | 'javascript' | 'python' | 'unknown'
|
|
2677
|
+
symbols: DocSymbol[]
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
// Simulated scanFile — replace with the real import in your project
|
|
2681
|
+
async function scanFile(filePath: string): Promise<ScanResult> {
|
|
2682
|
+
const ext = filePath.split('.').pop()?.toLowerCase()
|
|
2683
|
+
|
|
2684
|
+
const langMap: Record<string, ScanResult['language']> = {
|
|
2685
|
+
ts: 'typescript',
|
|
2686
|
+
tsx: 'typescript',
|
|
2687
|
+
js: 'javascript',
|
|
2688
|
+
jsx: 'javascript',
|
|
2689
|
+
py: 'python',
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
const language = langMap[ext ?? ''] ?? 'unknown'
|
|
2693
|
+
|
|
2694
|
+
if (language === 'unknown') {
|
|
2695
|
+
throw new Error(`No scanner available for file extension: .${ext}`)
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
// Simulated scan output — real implementation parses the AST
|
|
2699
|
+
return {
|
|
2700
|
+
filePath,
|
|
2701
|
+
language,
|
|
2702
|
+
symbols: [
|
|
2703
|
+
{
|
|
2704
|
+
name: 'getUserById',
|
|
2705
|
+
kind: 'function',
|
|
2706
|
+
line: 12,
|
|
2707
|
+
docstring: 'Fetch a user record by their unique ID.',
|
|
2708
|
+
signature: 'async function getUserById(id: string): Promise<User>',
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
name: 'User',
|
|
2712
|
+
kind: 'interface',
|
|
2713
|
+
line: 4,
|
|
2714
|
+
docstring: 'Represents an authenticated user.',
|
|
2715
|
+
signature: 'interface User { id: string; email: string; role: string }',
|
|
2716
|
+
},
|
|
2717
|
+
],
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// --- Usage ---
|
|
2722
|
+
async function main() {
|
|
2723
|
+
const targetFile = process.env.SCAN_TARGET || './src/users.ts'
|
|
2724
|
+
|
|
2725
|
+
try {
|
|
2726
|
+
const result = await scanFile(targetFile)
|
|
2727
|
+
|
|
2728
|
+
console.log(`Scanned: ${result.filePath}`)
|
|
2729
|
+
console.log(`Language: ${result.language}`)
|
|
2730
|
+
console.log(`Symbols found: ${result.symbols.length}`)
|
|
2731
|
+
|
|
2732
|
+
for (const symbol of result.symbols) {
|
|
2733
|
+
console.log(`\n [${symbol.kind}] ${symbol.name} (line ${symbol.line})`)
|
|
2734
|
+
if (symbol.docstring) console.log(` 📝 ${symbol.docstring}`)
|
|
2735
|
+
if (symbol.signature) console.log(` 🔷 ${symbol.signature}`)
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
// Expected output:
|
|
2739
|
+
// Scanned: ./src/users.ts
|
|
2740
|
+
// Language: typescript
|
|
2741
|
+
// Symbols found: 2
|
|
2742
|
+
//
|
|
2743
|
+
// [function] getUserById (line 12)
|
|
2744
|
+
// 📝 Fetch a user record by their unique ID.
|
|
2745
|
+
// 🔷 async function getUserById(id: string): Promise<User>
|
|
2746
|
+
//
|
|
2747
|
+
// [interface] User (line 4)
|
|
2748
|
+
// 📝 Represents an authenticated user.
|
|
2749
|
+
// 🔷 interface User { id: string; email: string; role: string }
|
|
2750
|
+
} catch (error) {
|
|
2751
|
+
console.error('Scan failed:', error instanceof Error ? error.message : error)
|
|
2752
|
+
process.exit(1)
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
main()
|
|
2757
|
+
```
|
|
2758
|
+
|
|
2759
|
+
### Related
|
|
2760
|
+
|
|
2761
|
+
<CardGroup cols={3}>
|
|
2762
|
+
<Card title="canHandle" icon="link" href="#canhandle">
|
|
2763
|
+
Uses
|
|
2764
|
+
</Card>
|
|
2765
|
+
<Card title="canHandle" icon="link" href="#canhandle">
|
|
2766
|
+
Uses
|
|
2767
|
+
</Card>
|
|
2768
|
+
<Card title="canHandle" icon="link" href="#canhandle">
|
|
2769
|
+
Uses
|
|
2770
|
+
</Card>
|
|
2771
|
+
<Card title="canHandle" icon="link" href="#canhandle">
|
|
2772
|
+
Uses
|
|
2773
|
+
</Card>
|
|
2774
|
+
<Card title="GoScanner" icon="link" href="#goscanner">
|
|
2775
|
+
Used by
|
|
2776
|
+
</Card>
|
|
2777
|
+
<Card title="canHandle" icon="link" href="#canhandle">
|
|
2778
|
+
Used by
|
|
2779
|
+
</Card>
|
|
2780
|
+
</CardGroup>
|
|
2781
|
+
|
|
2782
|
+
---
|
|
2783
|
+
|
|
2784
|
+
|
|
2785
|
+
## `constructor`
|
|
2786
|
+
|
|
2787
|
+
```typescript
|
|
2788
|
+
constructor(sourcePath: string, outputPath: string, config: Record<string, unknown> = {})
|
|
2789
|
+
```
|
|
2790
|
+
|
|
2791
|
+
Use this to initialize a plugin manager that coordinates documentation plugins across a source codebase, wiring together input/output paths and shared configuration before any plugins are registered or executed.
|
|
2792
|
+
|
|
2793
|
+
## Parameters
|
|
2794
|
+
|
|
2795
|
+
| Name | Type | Required | Description |
|
|
2796
|
+
|------|------|----------|-------------|
|
|
2797
|
+
| `sourcePath` | `string` | ✅ Yes | Absolute or relative path to the source code directory to be processed |
|
|
2798
|
+
| `outputPath` | `string` | ✅ Yes | Absolute or relative path to the directory where generated docs will be written |
|
|
2799
|
+
| `config` | `Record<string, unknown>` | ❌ No | Optional key-value configuration passed to all plugins via shared context. Defaults to `{}` |
|
|
2800
|
+
|
|
2801
|
+
## Returns
|
|
2802
|
+
|
|
2803
|
+
A `PluginManager` instance with an initialized internal `PluginContext` containing the provided paths and config. The plugin registry starts empty — use subsequent `register`/`load` methods to add plugins.
|
|
2804
|
+
|
|
2805
|
+
## Notes
|
|
2806
|
+
- `sourcePath` and `outputPath` are stored on the shared `PluginContext`, making them accessible to every plugin that runs
|
|
2807
|
+
- Passing `config` here is the recommended way to share global settings (e.g., output format, version flags) across all plugins without coupling them directly
|
|
2808
|
+
|
|
2809
|
+
### Example
|
|
2810
|
+
|
|
2811
|
+
```typescript example.ts
|
|
2812
|
+
// --- Inline types (mirroring the real autodocs internals) ---
|
|
2813
|
+
interface PluginContext {
|
|
2814
|
+
sourcePath: string
|
|
2815
|
+
outputPath: string
|
|
2816
|
+
config: Record<string, unknown>
|
|
2817
|
+
logger: {
|
|
2818
|
+
info: (msg: string) => void
|
|
2819
|
+
warn: (msg: string) => void
|
|
2820
|
+
error: (msg: string) => void
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
interface SkryptPlugin {
|
|
2825
|
+
name: string
|
|
2826
|
+
run: (context: PluginContext) => Promise<void>
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// --- Self-contained PluginManager implementation ---
|
|
2830
|
+
class PluginManager {
|
|
2831
|
+
private plugins: SkryptPlugin[] = []
|
|
2832
|
+
private context: PluginContext
|
|
2833
|
+
|
|
2834
|
+
constructor(
|
|
2835
|
+
sourcePath: string,
|
|
2836
|
+
outputPath: string,
|
|
2837
|
+
config: Record<string, unknown> = {}
|
|
2838
|
+
) {
|
|
2839
|
+
this.context = {
|
|
2840
|
+
sourcePath,
|
|
2841
|
+
outputPath,
|
|
2842
|
+
config,
|
|
2843
|
+
logger: {
|
|
2844
|
+
info: (msg: string) => console.log(`[INFO] ${msg}`),
|
|
2845
|
+
warn: (msg: string) => console.warn(`[WARN] ${msg}`),
|
|
2846
|
+
error: (msg: string) => console.error(`[ERROR] ${msg}`),
|
|
2847
|
+
},
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
/** Expose context for inspection in this example */
|
|
2852
|
+
getContext(): PluginContext {
|
|
2853
|
+
return this.context
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
register(plugin: SkryptPlugin): void {
|
|
2857
|
+
this.plugins.push(plugin)
|
|
2858
|
+
this.context.logger.info(`Registered plugin: ${plugin.name}`)
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
async runAll(): Promise<void> {
|
|
2862
|
+
for (const plugin of this.plugins) {
|
|
2863
|
+
this.context.logger.info(`Running plugin: ${plugin.name}`)
|
|
2864
|
+
await plugin.run(this.context)
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
// --- Usage example ---
|
|
2870
|
+
async function main() {
|
|
2871
|
+
try {
|
|
2872
|
+
const manager = new PluginManager(
|
|
2873
|
+
process.env.SOURCE_PATH || './src',
|
|
2874
|
+
process.env.OUTPUT_PATH || './docs',
|
|
2875
|
+
{
|
|
2876
|
+
outputFormat: 'markdown',
|
|
2877
|
+
version: '2.1.0',
|
|
2878
|
+
includePrivate: false,
|
|
2879
|
+
}
|
|
2880
|
+
)
|
|
2881
|
+
|
|
2882
|
+
// Inspect the initialized context
|
|
2883
|
+
const ctx = manager.getContext()
|
|
2884
|
+
console.log('PluginManager initialized:')
|
|
2885
|
+
console.log(' sourcePath:', ctx.sourcePath) // './src'
|
|
2886
|
+
console.log(' outputPath:', ctx.outputPath) // './docs'
|
|
2887
|
+
console.log(' config:', ctx.config)
|
|
2888
|
+
// {
|
|
2889
|
+
// outputFormat: 'markdown',
|
|
2890
|
+
// version: '2.1.0',
|
|
2891
|
+
// includePrivate: false
|
|
2892
|
+
// }
|
|
2893
|
+
|
|
2894
|
+
// Register a sample plugin to confirm context flows through
|
|
2895
|
+
manager.register({
|
|
2896
|
+
name: 'typescript-extractor',
|
|
2897
|
+
run: async (context) => {
|
|
2898
|
+
console.log(`\n[typescript-extractor] Reading from: ${context.sourcePath}`)
|
|
2899
|
+
console.log(`[typescript-extractor] Writing to: ${context.outputPath}`)
|
|
2900
|
+
console.log(`[typescript-extractor] Format: ${context.config.outputFormat}`)
|
|
2901
|
+
},
|
|
2902
|
+
})
|
|
2903
|
+
|
|
2904
|
+
await manager.runAll()
|
|
2905
|
+
// Output:
|
|
2906
|
+
// [INFO] Registered plugin: typescript-extractor
|
|
2907
|
+
// [INFO] Running plugin: typescript-extractor
|
|
2908
|
+
// [typescript-extractor] Reading from: ./src
|
|
2909
|
+
// [typescript-extractor] Writing to: ./docs
|
|
2910
|
+
// [typescript-extractor] Format: markdown
|
|
2911
|
+
} catch (error) {
|
|
2912
|
+
console.error('PluginManager setup failed:', error)
|
|
2913
|
+
process.exit(1)
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
main()
|
|
2918
|
+
```
|
|
2919
|
+
|
|
2920
|
+
### Related
|
|
2921
|
+
|
|
2922
|
+
<CardGroup cols={3}>
|
|
2923
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
2924
|
+
Uses
|
|
2925
|
+
</Card>
|
|
2926
|
+
<Card title="AnthropicClient" icon="link" href="#anthropicclient">
|
|
2927
|
+
Used by
|
|
2928
|
+
</Card>
|
|
2929
|
+
<Card title="OpenAICompatibleClient" icon="link" href="#openaicompatibleclient">
|
|
2930
|
+
Used by
|
|
2931
|
+
</Card>
|
|
2932
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
2933
|
+
Used by
|
|
2934
|
+
</Card>
|
|
2935
|
+
<Card title="AnthropicClient" icon="link" href="#anthropicclient">
|
|
2936
|
+
Related
|
|
2937
|
+
</Card>
|
|
2938
|
+
<Card title="OpenAICompatibleClient" icon="link" href="#openaicompatibleclient">
|
|
2939
|
+
Related
|
|
2940
|
+
</Card>
|
|
2941
|
+
</CardGroup>
|
|
2942
|
+
|
|
2943
|
+
---
|
|
2944
|
+
|
|
2945
|
+
|
|
2946
|
+
## `loadPlugins`
|
|
2947
|
+
|
|
2948
|
+
```typescript
|
|
2949
|
+
async loadPlugins(configPath?: string): Promise<void>
|
|
2950
|
+
```
|
|
2951
|
+
|
|
2952
|
+
Use this to initialize and load plugins into the `PluginManager` from a configuration file, enabling plugin-based extensibility in your application.
|
|
2953
|
+
|
|
2954
|
+
This method reads plugin definitions from a `skrypt.config.js` or `skrypt.config.ts` file (auto-discovered if no path is provided) and registers them with the manager. Call this once during application startup before executing any plugin-dependent logic.
|
|
2955
|
+
|
|
2956
|
+
## Parameters
|
|
2957
|
+
|
|
2958
|
+
| Name | Type | Required | Description |
|
|
2959
|
+
|------|------|----------|-------------|
|
|
2960
|
+
| `configPath` | `string` | No | Absolute or relative path to a config file. If omitted, the manager auto-discovers `skrypt.config.js` or `skrypt.config.ts` in the current working directory. |
|
|
2961
|
+
|
|
2962
|
+
## Returns
|
|
2963
|
+
|
|
2964
|
+
Returns `Promise<void>`. Resolves when all plugins have been loaded and registered. If no config file is found, resolves immediately without error.
|
|
2965
|
+
|
|
2966
|
+
## Behavior Notes
|
|
2967
|
+
|
|
2968
|
+
- **No config found** → resolves silently with no plugins loaded
|
|
2969
|
+
- **Config found, valid** → plugins are registered and ready to use
|
|
2970
|
+
- **Config found, invalid** → throws or logs an error depending on the failure type
|
|
2971
|
+
- Passing an explicit `configPath` overrides auto-discovery entirely
|
|
2972
|
+
|
|
2973
|
+
### Example
|
|
2974
|
+
|
|
2975
|
+
```typescript example.ts
|
|
2976
|
+
// Inline types to simulate PluginManager behavior
|
|
2977
|
+
type Plugin = {
|
|
2978
|
+
name: string
|
|
2979
|
+
execute: (input: string) => string
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
type PluginConfig = {
|
|
2983
|
+
plugins: Plugin[]
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
// Simulated PluginManager class (self-contained, no external imports)
|
|
2987
|
+
class PluginManager {
|
|
2988
|
+
private plugins: Map<string, Plugin> = new Map()
|
|
2989
|
+
private configSearchPaths = ['./skrypt.config.js', './skrypt.config.ts']
|
|
2990
|
+
|
|
2991
|
+
private findConfigFile(): string | null {
|
|
2992
|
+
// In real usage, this checks the filesystem via existsSync
|
|
2993
|
+
// Here we simulate "not found" unless overridden
|
|
2994
|
+
return null
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
private async importConfig(configPath: string): Promise<PluginConfig> {
|
|
2998
|
+
// Simulate loading a config file by returning a mock config
|
|
2999
|
+
console.log(`[plugin] Loading config from: ${configPath}`)
|
|
3000
|
+
return {
|
|
3001
|
+
plugins: [
|
|
3002
|
+
{ name: 'markdown-renderer', execute: (input) => `<p>${input}</p>` },
|
|
3003
|
+
{ name: 'syntax-highlighter', execute: (input) => `<code>${input}</code>` },
|
|
3004
|
+
],
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
async loadPlugins(configPath?: string): Promise<void> {
|
|
3009
|
+
const configFile = configPath || this.findConfigFile()
|
|
3010
|
+
|
|
3011
|
+
if (!configFile) {
|
|
3012
|
+
console.log('[plugin] No config file found — skipping plugin load')
|
|
3013
|
+
return
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
try {
|
|
3017
|
+
const config = await this.importConfig(configFile)
|
|
3018
|
+
|
|
3019
|
+
for (const plugin of config.plugins) {
|
|
3020
|
+
this.plugins.set(plugin.name, plugin)
|
|
3021
|
+
console.log(`[plugin] Registered: ${plugin.name}`)
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
console.log(`[plugin] Loaded ${this.plugins.size} plugin(s) successfully`)
|
|
3025
|
+
} catch (error) {
|
|
3026
|
+
console.error('[plugin] Failed to load plugins:', error)
|
|
3027
|
+
throw error
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
getPlugin(name: string): Plugin | undefined {
|
|
3032
|
+
return this.plugins.get(name)
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
listPlugins(): string[] {
|
|
3036
|
+
return Array.from(this.plugins.keys())
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
// --- Usage ---
|
|
3041
|
+
async function main() {
|
|
3042
|
+
const manager = new PluginManager()
|
|
3043
|
+
|
|
3044
|
+
try {
|
|
3045
|
+
// Option 1: Auto-discover config (resolves silently if not found)
|
|
3046
|
+
await manager.loadPlugins()
|
|
3047
|
+
|
|
3048
|
+
// Option 2: Explicit config path (e.g., from env or CLI arg)
|
|
3049
|
+
const configPath = process.env.PLUGIN_CONFIG_PATH || './skrypt.config.js'
|
|
3050
|
+
await manager.loadPlugins(configPath)
|
|
3051
|
+
|
|
3052
|
+
// Plugins are now available
|
|
3053
|
+
const availablePlugins = manager.listPlugins()
|
|
3054
|
+
console.log('Available plugins:', availablePlugins)
|
|
3055
|
+
// Output: Available plugins: [ 'markdown-renderer', 'syntax-highlighter' ]
|
|
3056
|
+
|
|
3057
|
+
const renderer = manager.getPlugin('markdown-renderer')
|
|
3058
|
+
if (renderer) {
|
|
3059
|
+
const output = renderer.execute('Hello, world!')
|
|
3060
|
+
console.log('Renderer output:', output)
|
|
3061
|
+
// Output: Renderer output: <p>Hello, world!</p>
|
|
3062
|
+
}
|
|
3063
|
+
} catch (error) {
|
|
3064
|
+
console.error('Startup failed — could not load plugins:', error)
|
|
3065
|
+
process.exit(1)
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
main()
|
|
3070
|
+
```
|
|
3071
|
+
|
|
3072
|
+
### Related
|
|
3073
|
+
|
|
3074
|
+
<CardGroup cols={3}>
|
|
3075
|
+
<Card title="findConfigFile" icon="link" href="#findconfigfile">
|
|
3076
|
+
Uses
|
|
3077
|
+
</Card>
|
|
3078
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
3079
|
+
Related
|
|
3080
|
+
</Card>
|
|
3081
|
+
</CardGroup>
|
|
3082
|
+
|
|
3083
|
+
---
|
|
3084
|
+
|
|
3085
|
+
|
|
3086
|
+
## `onAfterGenerate`
|
|
3087
|
+
|
|
3088
|
+
```typescript
|
|
3089
|
+
async onAfterGenerate<T>(docs: T[]): Promise<T[]>
|
|
3090
|
+
```
|
|
3091
|
+
|
|
3092
|
+
Use this to run all registered plugins' `onAfterGenerate` hooks against a collection of generated documentation objects, allowing plugins to transform, filter, enrich, or reorder docs before they are written to disk.
|
|
3093
|
+
|
|
3094
|
+
This is the final transformation stage in the generation pipeline — ideal for plugins that need to post-process docs (e.g., injecting metadata, removing internal-only entries, sorting output).
|
|
3095
|
+
|
|
3096
|
+
## Parameters
|
|
3097
|
+
|
|
3098
|
+
| Name | Type | Required | Description |
|
|
3099
|
+
|------|------|----------|-------------|
|
|
3100
|
+
| `docs` | `T[]` | Yes | The array of generated documentation objects to pass through all registered plugin hooks. The generic type `T` can be any doc shape. |
|
|
3101
|
+
|
|
3102
|
+
## Returns
|
|
3103
|
+
|
|
3104
|
+
| Condition | Returns |
|
|
3105
|
+
|-----------|---------|
|
|
3106
|
+
| A plugin hook transforms the docs | `Promise<T[]>` — the transformed array returned by the plugin |
|
|
3107
|
+
| No plugin hook is registered / hook returns falsy | `Promise<T[]>` — the original `docs` array, unchanged |
|
|
3108
|
+
|
|
3109
|
+
The method always resolves to a `T[]` — it never returns `undefined` or `null`.
|
|
3110
|
+
|
|
3111
|
+
### Example
|
|
3112
|
+
|
|
3113
|
+
```typescript example.ts
|
|
3114
|
+
// Inline types to simulate the PluginManager behavior
|
|
3115
|
+
type Hook<T> = (data: T) => Promise<T> | T
|
|
3116
|
+
|
|
3117
|
+
interface Plugin {
|
|
3118
|
+
name: string
|
|
3119
|
+
onAfterGenerate?: Hook<any[]>
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
interface DocEntry {
|
|
3123
|
+
id: string
|
|
3124
|
+
title: string
|
|
3125
|
+
content: string
|
|
3126
|
+
internal?: boolean
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// Minimal self-contained PluginManager simulation
|
|
3130
|
+
class PluginManager {
|
|
3131
|
+
private plugins: Plugin[] = []
|
|
3132
|
+
|
|
3133
|
+
register(plugin: Plugin) {
|
|
3134
|
+
this.plugins.push(plugin)
|
|
3135
|
+
console.log(`Plugin registered: ${plugin.name}`)
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
private async runHook<T>(hookName: keyof Plugin, data: T): Promise<T | undefined> {
|
|
3139
|
+
let result: T = data
|
|
3140
|
+
for (const plugin of this.plugins) {
|
|
3141
|
+
const hook = plugin[hookName] as Hook<T> | undefined
|
|
3142
|
+
if (typeof hook === 'function') {
|
|
3143
|
+
result = await hook(result)
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
return this.plugins.some(p => typeof p[hookName] === 'function') ? result : undefined
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
async onAfterGenerate<T>(docs: T[]): Promise<T[]> {
|
|
3150
|
+
return (await this.runHook<T[]>('onAfterGenerate', docs)) || docs
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// --- Example usage ---
|
|
3155
|
+
|
|
3156
|
+
const manager = new PluginManager()
|
|
3157
|
+
|
|
3158
|
+
// Plugin 1: Remove internal-only docs
|
|
3159
|
+
manager.register({
|
|
3160
|
+
name: 'filter-internal-plugin',
|
|
3161
|
+
onAfterGenerate: async (docs: DocEntry[]) => {
|
|
3162
|
+
const filtered = docs.filter(doc => !doc.internal)
|
|
3163
|
+
console.log(`[filter-internal-plugin] Removed ${docs.length - filtered.length} internal doc(s)`)
|
|
3164
|
+
return filtered
|
|
3165
|
+
}
|
|
3166
|
+
})
|
|
3167
|
+
|
|
3168
|
+
// Plugin 2: Inject a generated timestamp into each doc
|
|
3169
|
+
manager.register({
|
|
3170
|
+
name: 'timestamp-plugin',
|
|
3171
|
+
onAfterGenerate: async (docs: DocEntry[]) => {
|
|
3172
|
+
return docs.map(doc => ({
|
|
3173
|
+
...doc,
|
|
3174
|
+
content: `${doc.content}\n\n_Generated at: 2024-01-15T10:30:00Z_`
|
|
3175
|
+
}))
|
|
3176
|
+
}
|
|
3177
|
+
})
|
|
3178
|
+
|
|
3179
|
+
const generatedDocs: DocEntry[] = [
|
|
3180
|
+
{ id: 'doc-001', title: 'Getting Started', content: 'Install the package...' },
|
|
3181
|
+
{ id: 'doc-002', title: 'Internal Roadmap', content: 'Q3 plans...', internal: true },
|
|
3182
|
+
{ id: 'doc-003', title: 'API Reference', content: 'All public methods...' },
|
|
3183
|
+
]
|
|
3184
|
+
|
|
3185
|
+
async function main() {
|
|
3186
|
+
try {
|
|
3187
|
+
console.log(`\nInput docs: ${generatedDocs.length}`)
|
|
3188
|
+
|
|
3189
|
+
const finalDocs = await manager.onAfterGenerate(generatedDocs)
|
|
3190
|
+
|
|
3191
|
+
console.log(`\nOutput docs: ${finalDocs.length}`)
|
|
3192
|
+
finalDocs.forEach(doc => {
|
|
3193
|
+
console.log(`\n--- ${doc.title} ---`)
|
|
3194
|
+
console.log(doc.content)
|
|
3195
|
+
})
|
|
3196
|
+
|
|
3197
|
+
// Expected output:
|
|
3198
|
+
// Input docs: 3
|
|
3199
|
+
// [filter-internal-plugin] Removed 1 internal doc(s)
|
|
3200
|
+
// Output docs: 2
|
|
3201
|
+
// --- Getting Started ---
|
|
3202
|
+
// Install the package...
|
|
3203
|
+
// _Generated at: 2024-01-15T10:30:00Z_
|
|
3204
|
+
// --- API Reference ---
|
|
3205
|
+
// All public methods...
|
|
3206
|
+
// _Generated at: 2024-01-15T10:30:00Z_
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
console.error('onAfterGenerate failed:', error)
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
main()
|
|
3213
|
+
```
|
|
3214
|
+
|
|
3215
|
+
### Related
|
|
3216
|
+
|
|
3217
|
+
<CardGroup cols={3}>
|
|
3218
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
3219
|
+
Uses
|
|
3220
|
+
</Card>
|
|
3221
|
+
<Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
|
|
3222
|
+
Uses
|
|
3223
|
+
</Card>
|
|
3224
|
+
<Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
|
|
3225
|
+
Uses
|
|
3226
|
+
</Card>
|
|
3227
|
+
<Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
|
|
3228
|
+
Used by
|
|
3229
|
+
</Card>
|
|
3230
|
+
<Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
|
|
3231
|
+
Used by
|
|
3232
|
+
</Card>
|
|
3233
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
3234
|
+
Related
|
|
3235
|
+
</Card>
|
|
3236
|
+
</CardGroup>
|
|
3237
|
+
|
|
3238
|
+
---
|
|
3239
|
+
|
|
3240
|
+
|
|
3241
|
+
## `onAfterScan`
|
|
3242
|
+
|
|
3243
|
+
```typescript
|
|
3244
|
+
async onAfterScan<T>(elements: T[]): Promise<T[]>
|
|
3245
|
+
```
|
|
3246
|
+
|
|
3247
|
+
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.
|
|
3248
|
+
|
|
3249
|
+
This is called automatically by the `PluginManager` after a scan 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.
|
|
3250
|
+
|
|
3251
|
+
## Parameters
|
|
3252
|
+
|
|
3253
|
+
| Name | Type | Required | Description |
|
|
3254
|
+
|------|------|----------|-------------|
|
|
3255
|
+
| `elements` | `T[]` | Yes | The array of scanned elements to pass through the plugin hook pipeline |
|
|
3256
|
+
|
|
3257
|
+
## Returns
|
|
3258
|
+
|
|
3259
|
+
**`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.
|
|
3260
|
+
|
|
3261
|
+
### Example
|
|
3262
|
+
|
|
3263
|
+
```typescript example.ts
|
|
3264
|
+
// Inline types to simulate PluginManager behavior
|
|
3265
|
+
type Plugin = {
|
|
3266
|
+
name: string
|
|
3267
|
+
onAfterScan?: <T>(elements: T[]) => Promise<T[]>
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
type ScannedFile = {
|
|
3271
|
+
path: string
|
|
3272
|
+
type: string
|
|
3273
|
+
exported: boolean
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
// Simulate the PluginManager class
|
|
3277
|
+
class PluginManager {
|
|
3278
|
+
private plugins: Plugin[] = []
|
|
3279
|
+
|
|
3280
|
+
register(plugin: Plugin) {
|
|
3281
|
+
this.plugins.push(plugin)
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
private async runHook<T>(hookName: string, arg?: T): Promise<T | undefined> {
|
|
3285
|
+
let result = arg
|
|
3286
|
+
for (const plugin of this.plugins) {
|
|
3287
|
+
const hook = (plugin as any)[hookName]
|
|
3288
|
+
if (typeof hook === 'function') {
|
|
3289
|
+
const hookResult = await hook.call(plugin, result)
|
|
3290
|
+
if (hookResult !== undefined) {
|
|
3291
|
+
result = hookResult
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
return result
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
async onAfterScan<T>(elements: T[]): Promise<T[]> {
|
|
3299
|
+
return (await this.runHook<T[]>('onAfterScan', elements)) || elements
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
// Example plugins that transform scanned elements
|
|
3304
|
+
const exportFilterPlugin: Plugin = {
|
|
3305
|
+
name: 'export-filter',
|
|
3306
|
+
async onAfterScan<T>(elements: T[]): Promise<T[]> {
|
|
3307
|
+
const files = elements as unknown as ScannedFile[]
|
|
3308
|
+
const filtered = files.filter(f => f.exported)
|
|
3309
|
+
console.log(`[export-filter] Filtered ${elements.length} → ${filtered.length} exported files`)
|
|
3310
|
+
return filtered as unknown as T[]
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
const pathNormalizerPlugin: Plugin = {
|
|
3315
|
+
name: 'path-normalizer',
|
|
3316
|
+
async onAfterScan<T>(elements: T[]): Promise<T[]> {
|
|
3317
|
+
const files = elements as unknown as ScannedFile[]
|
|
3318
|
+
const normalized = files.map(f => ({ ...f, path: f.path.replace(/\\/g, '/') }))
|
|
3319
|
+
console.log(`[path-normalizer] Normalized ${normalized.length} file paths`)
|
|
3320
|
+
return normalized as unknown as T[]
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
async function main() {
|
|
3325
|
+
try {
|
|
3326
|
+
const manager = new PluginManager()
|
|
3327
|
+
manager.register(exportFilterPlugin)
|
|
3328
|
+
manager.register(pathNormalizerPlugin)
|
|
3329
|
+
|
|
3330
|
+
const scannedFiles: ScannedFile[] = [
|
|
3331
|
+
{ path: 'src\\components\\Button.ts', type: 'component', exported: true },
|
|
3332
|
+
{ path: 'src\\utils\\internal.ts', type: 'utility', exported: false },
|
|
3333
|
+
{ path: 'src\\hooks\\useAuth.ts', type: 'hook', exported: true },
|
|
3334
|
+
{ path: 'src\\helpers\\debug.ts', type: 'helper', exported: false },
|
|
3335
|
+
]
|
|
3336
|
+
|
|
3337
|
+
console.log('Before onAfterScan:', scannedFiles.length, 'files')
|
|
3338
|
+
|
|
3339
|
+
const result = await manager.onAfterScan(scannedFiles)
|
|
3340
|
+
|
|
3341
|
+
console.log('\nAfter onAfterScan:', result.length, 'files')
|
|
3342
|
+
console.log('Final elements:', result)
|
|
3343
|
+
// Output:
|
|
3344
|
+
// Before onAfterScan: 4 files
|
|
3345
|
+
// [export-filter] Filtered 4 → 2 exported files
|
|
3346
|
+
// [path-normalizer] Normalized 2 file paths
|
|
3347
|
+
// After onAfterScan: 2 files
|
|
3348
|
+
// Final elements: [
|
|
3349
|
+
// { path: 'src/components/Button.ts', type: 'component', exported: true },
|
|
3350
|
+
// { path: 'src/hooks/useAuth.ts', type: 'hook', exported: true }
|
|
3351
|
+
// ]
|
|
3352
|
+
} catch (error) {
|
|
3353
|
+
console.error('onAfterScan failed:', error)
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
main()
|
|
3358
|
+
```
|
|
3359
|
+
|
|
3360
|
+
### Related
|
|
3361
|
+
|
|
3362
|
+
<CardGroup cols={3}>
|
|
3363
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
3364
|
+
Uses
|
|
3365
|
+
</Card>
|
|
3366
|
+
<Card title="onBeforeScan" icon="link" href="#onbeforescan">
|
|
3367
|
+
Uses
|
|
3368
|
+
</Card>
|
|
3369
|
+
<Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
|
|
3370
|
+
Uses
|
|
3371
|
+
</Card>
|
|
3372
|
+
<Card title="onBeforeScan" icon="link" href="#onbeforescan">
|
|
3373
|
+
Used by
|
|
3374
|
+
</Card>
|
|
3375
|
+
<Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
|
|
3376
|
+
Used by
|
|
3377
|
+
</Card>
|
|
3378
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
3379
|
+
Related
|
|
3380
|
+
</Card>
|
|
3381
|
+
</CardGroup>
|
|
3382
|
+
|
|
3383
|
+
---
|
|
3384
|
+
|
|
3385
|
+
|
|
3386
|
+
## `onAfterWrite`
|
|
3387
|
+
|
|
3388
|
+
```typescript
|
|
3389
|
+
async onAfterWrite(): Promise<void>
|
|
3390
|
+
```
|
|
3391
|
+
|
|
3392
|
+
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.
|
|
3393
|
+
|
|
3394
|
+
`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.
|
|
3395
|
+
|
|
3396
|
+
### Parameters
|
|
3397
|
+
|
|
3398
|
+
This method takes no parameters.
|
|
3399
|
+
|
|
3400
|
+
### Returns
|
|
3401
|
+
|
|
3402
|
+
| Type | Description |
|
|
3403
|
+
|------|-------------|
|
|
3404
|
+
| `Promise<void>` | Resolves when all plugin `onAfterWrite` hooks have completed. Does not return a value. |
|
|
3405
|
+
|
|
3406
|
+
### When to Call
|
|
3407
|
+
|
|
3408
|
+
Call `onAfterWrite` immediately after all documentation files have been written. It pairs with `onBeforeWrite` to form a write lifecycle:
|
|
3409
|
+
|
|
3410
|
+
1. `onBeforeWrite(docs)` — transform/filter docs before writing
|
|
3411
|
+
2. *(write files to disk)*
|
|
3412
|
+
3. `onAfterWrite()` — notify plugins that writing is complete
|
|
3413
|
+
|
|
3414
|
+
### Example
|
|
3415
|
+
|
|
3416
|
+
```typescript example.ts
|
|
3417
|
+
// Inline types to keep example self-contained
|
|
3418
|
+
type Hook = (...args: unknown[]) => Promise<unknown> | unknown
|
|
3419
|
+
|
|
3420
|
+
interface Plugin {
|
|
3421
|
+
name: string
|
|
3422
|
+
onAfterWrite?: () => Promise<void> | void
|
|
3423
|
+
onBeforeWrite?: <T>(docs: T[]) => Promise<T[]> | T[]
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
// Simulated PluginManager implementation
|
|
3427
|
+
class PluginManager {
|
|
3428
|
+
private plugins: Plugin[] = []
|
|
3429
|
+
|
|
3430
|
+
register(plugin: Plugin): void {
|
|
3431
|
+
this.plugins.push(plugin)
|
|
3432
|
+
console.log(`Plugin registered: ${plugin.name}`)
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
async runHook(hookName: keyof Plugin, ...args: unknown[]): Promise<unknown> {
|
|
3436
|
+
let result: unknown
|
|
3437
|
+
for (const plugin of this.plugins) {
|
|
3438
|
+
const hook = plugin[hookName] as Hook | undefined
|
|
3439
|
+
if (typeof hook === 'function') {
|
|
3440
|
+
result = await hook(...args)
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
return result
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
async onBeforeWrite<T>(docs: T[]): Promise<T[]> {
|
|
3447
|
+
return (await this.runHook('onBeforeWrite', docs) as T[]) || docs
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
async onAfterWrite(): Promise<void> {
|
|
3451
|
+
await this.runHook('onAfterWrite')
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
// --- Example usage ---
|
|
3456
|
+
|
|
3457
|
+
const manager = new PluginManager()
|
|
3458
|
+
|
|
3459
|
+
// Register a plugin that logs after writing
|
|
3460
|
+
manager.register({
|
|
3461
|
+
name: 'cache-invalidation-plugin',
|
|
3462
|
+
onAfterWrite: async () => {
|
|
3463
|
+
console.log('[cache-invalidation-plugin] Cache cleared after write.')
|
|
3464
|
+
}
|
|
3465
|
+
})
|
|
3466
|
+
|
|
3467
|
+
// Register a plugin that sends a notification
|
|
3468
|
+
manager.register({
|
|
3469
|
+
name: 'notify-plugin',
|
|
3470
|
+
onAfterWrite: async () => {
|
|
3471
|
+
const webhookUrl = process.env.NOTIFY_WEBHOOK_URL || 'https://hooks.example.com/notify'
|
|
3472
|
+
console.log(`[notify-plugin] POST notification sent to: ${webhookUrl}`)
|
|
3473
|
+
}
|
|
3474
|
+
})
|
|
3475
|
+
|
|
3476
|
+
async function runWriteLifecycle() {
|
|
3477
|
+
try {
|
|
3478
|
+
const docs = [
|
|
3479
|
+
{ id: 'doc-001', content: 'Getting Started' },
|
|
3480
|
+
{ id: 'doc-002', content: 'API Reference' }
|
|
3481
|
+
]
|
|
3482
|
+
|
|
3483
|
+
// Step 1: Pre-write hook
|
|
3484
|
+
const processedDocs = await manager.onBeforeWrite(docs)
|
|
3485
|
+
console.log(`Writing ${processedDocs.length} docs to disk...`)
|
|
3486
|
+
|
|
3487
|
+
// Step 2: (Simulated) write to disk
|
|
3488
|
+
processedDocs.forEach(doc => {
|
|
3489
|
+
console.log(` Written: ${doc.id}.md`)
|
|
3490
|
+
})
|
|
3491
|
+
|
|
3492
|
+
// Step 3: Post-write hook — notify all plugins
|
|
3493
|
+
await manager.onAfterWrite()
|
|
3494
|
+
|
|
3495
|
+
console.log('Write lifecycle complete.')
|
|
3496
|
+
// Expected output:
|
|
3497
|
+
// Plugin registered: cache-invalidation-plugin
|
|
3498
|
+
// Plugin registered: notify-plugin
|
|
3499
|
+
// Writing 2 docs to disk...
|
|
3500
|
+
// Written: doc-001.md
|
|
3501
|
+
// Written: doc-002.md
|
|
3502
|
+
// [cache-invalidation-plugin] Cache cleared after write.
|
|
3503
|
+
// [notify-plugin] POST notification sent to: https://hooks.example.com/notify
|
|
3504
|
+
// Write lifecycle complete.
|
|
3505
|
+
} catch (error) {
|
|
3506
|
+
console.error('Write lifecycle failed:', error)
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
runWriteLifecycle()
|
|
3511
|
+
```
|
|
3512
|
+
|
|
3513
|
+
### Related
|
|
3514
|
+
|
|
3515
|
+
<CardGroup cols={3}>
|
|
3516
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
3517
|
+
Uses
|
|
3518
|
+
</Card>
|
|
3519
|
+
<Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
|
|
3520
|
+
Uses
|
|
3521
|
+
</Card>
|
|
3522
|
+
<Card title="transformContent" icon="link" href="#transformcontent">
|
|
3523
|
+
Uses
|
|
3524
|
+
</Card>
|
|
3525
|
+
<Card title="onBeforeWrite" icon="link" href="#onbeforewrite">
|
|
3526
|
+
Used by
|
|
3527
|
+
</Card>
|
|
3528
|
+
<Card title="transformContent" icon="link" href="#transformcontent">
|
|
3529
|
+
Used by
|
|
3530
|
+
</Card>
|
|
3531
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
3532
|
+
Related
|
|
3533
|
+
</Card>
|
|
3534
|
+
</CardGroup>
|
|
3535
|
+
|
|
3536
|
+
---
|
|
3537
|
+
|
|
3538
|
+
|
|
3539
|
+
## `onBeforeGenerate`
|
|
3540
|
+
|
|
3541
|
+
```typescript
|
|
3542
|
+
async onBeforeGenerate<T>(elements: T[]): Promise<T[]>
|
|
3543
|
+
```
|
|
3544
|
+
|
|
3545
|
+
Use this to run all registered plugins' `onBeforeGenerate` hooks against a collection of elements before documentation generation begins. This is the ideal interception point for filtering, transforming, sorting, or enriching your elements (e.g., parsed AST nodes, function signatures, class definitions) before they are handed off to the doc generator.
|
|
3546
|
+
|
|
3547
|
+
If no plugin hook modifies the elements, the original array is returned unchanged.
|
|
3548
|
+
|
|
3549
|
+
## Parameters
|
|
3550
|
+
|
|
3551
|
+
| Name | Type | Required | Description |
|
|
3552
|
+
|------|------|----------|-------------|
|
|
3553
|
+
| `elements` | `T[]` | Yes | The array of elements (e.g., parsed code nodes, doc entries) to be processed by registered plugin hooks before generation |
|
|
3554
|
+
|
|
3555
|
+
## Returns
|
|
3556
|
+
|
|
3557
|
+
| Condition | Returns |
|
|
3558
|
+
|-----------|---------|
|
|
3559
|
+
| A plugin hook transforms the elements | `Promise<T[]>` — the modified array returned by the hook |
|
|
3560
|
+
| No plugin hook is registered or hook returns falsy | `Promise<T[]>` — the original `elements` array, unchanged |
|
|
3561
|
+
|
|
3562
|
+
### Example
|
|
3563
|
+
|
|
3564
|
+
```typescript example.ts
|
|
3565
|
+
// Inline types to simulate the PluginManager behavior
|
|
3566
|
+
type Hook<T> = (elements: T[]) => Promise<T[] | null | undefined>
|
|
3567
|
+
|
|
3568
|
+
interface Plugin {
|
|
3569
|
+
onBeforeGenerate?: <T>(elements: T[]) => Promise<T[]>
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
// Simulated PluginManager with onBeforeGenerate support
|
|
3573
|
+
class PluginManager {
|
|
3574
|
+
private plugins: Plugin[] = []
|
|
3575
|
+
|
|
3576
|
+
register(plugin: Plugin) {
|
|
3577
|
+
this.plugins.push(plugin)
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
private async runHook<T>(hookName: keyof Plugin, elements: T[]): Promise<T[] | null> {
|
|
3581
|
+
let current: T[] = elements
|
|
3582
|
+
for (const plugin of this.plugins) {
|
|
3583
|
+
const hook = plugin[hookName] as Hook<T> | undefined
|
|
3584
|
+
if (typeof hook === 'function') {
|
|
3585
|
+
const result = await hook(current)
|
|
3586
|
+
if (result) current = result
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
return current.length ? current : null
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
async onBeforeGenerate<T>(elements: T[]): Promise<T[]> {
|
|
3593
|
+
return (await this.runHook<T>('onBeforeGenerate', elements)) || elements
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
// Simulated doc element type
|
|
3598
|
+
interface DocElement {
|
|
3599
|
+
name: string
|
|
3600
|
+
isPublic: boolean
|
|
3601
|
+
description: string
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
// Example: a plugin that filters out private elements before generation
|
|
3605
|
+
const publicOnlyPlugin: Plugin = {
|
|
3606
|
+
onBeforeGenerate: async <T>(elements: T[]): Promise<T[]> => {
|
|
3607
|
+
const docs = elements as unknown as DocElement[]
|
|
3608
|
+
const filtered = docs.filter((el) => el.isPublic)
|
|
3609
|
+
console.log(`[Plugin] Filtered ${docs.length - filtered.length} private element(s)`)
|
|
3610
|
+
return filtered as unknown as T[]
|
|
3611
|
+
},
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
// Example: a plugin that uppercases all names
|
|
3615
|
+
const uppercasePlugin: Plugin = {
|
|
3616
|
+
onBeforeGenerate: async <T>(elements: T[]): Promise<T[]> => {
|
|
3617
|
+
const docs = elements as unknown as DocElement[]
|
|
3618
|
+
const transformed = docs.map((el) => ({ ...el, name: el.name.toUpperCase() }))
|
|
3619
|
+
return transformed as unknown as T[]
|
|
3620
|
+
},
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
async function main() {
|
|
3624
|
+
const manager = new PluginManager()
|
|
3625
|
+
manager.register(publicOnlyPlugin)
|
|
3626
|
+
manager.register(uppercasePlugin)
|
|
3627
|
+
|
|
3628
|
+
const rawElements: DocElement[] = [
|
|
3629
|
+
{ name: 'getUserById', isPublic: true, description: 'Fetches a user by ID' },
|
|
3630
|
+
{ name: '_internalSync', isPublic: false, description: 'Internal sync helper' },
|
|
3631
|
+
{ name: 'createSession', isPublic: true, description: 'Creates a new session' },
|
|
3632
|
+
]
|
|
3633
|
+
|
|
3634
|
+
try {
|
|
3635
|
+
const processed = await manager.onBeforeGenerate(rawElements)
|
|
3636
|
+
console.log('Elements ready for generation:', processed)
|
|
3637
|
+
// Expected output:
|
|
3638
|
+
// [Plugin] Filtered 1 private element(s)
|
|
3639
|
+
// Elements ready for generation: [
|
|
3640
|
+
// { name: 'GETUSERBYID', isPublic: true, description: 'Fetches a user by ID' },
|
|
3641
|
+
// { name: 'CREATESESSION', isPublic: true, description: 'Creates a new session' }
|
|
3642
|
+
// ]
|
|
3643
|
+
} catch (error) {
|
|
3644
|
+
console.error('onBeforeGenerate failed:', error)
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
main()
|
|
3649
|
+
```
|
|
3650
|
+
|
|
3651
|
+
### Related
|
|
3652
|
+
|
|
3653
|
+
<CardGroup cols={3}>
|
|
3654
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
3655
|
+
Uses
|
|
3656
|
+
</Card>
|
|
3657
|
+
<Card title="onAfterScan" icon="link" href="#onafterscan">
|
|
3658
|
+
Uses
|
|
3659
|
+
</Card>
|
|
3660
|
+
<Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
|
|
3661
|
+
Uses
|
|
3662
|
+
</Card>
|
|
3663
|
+
<Card title="onAfterScan" icon="link" href="#onafterscan">
|
|
3664
|
+
Used by
|
|
3665
|
+
</Card>
|
|
3666
|
+
<Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
|
|
3667
|
+
Used by
|
|
3668
|
+
</Card>
|
|
3669
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
3670
|
+
Related
|
|
3671
|
+
</Card>
|
|
3672
|
+
</CardGroup>
|
|
3673
|
+
|
|
3674
|
+
---
|
|
3675
|
+
|
|
3676
|
+
|
|
3677
|
+
## `onBeforeScan`
|
|
3678
|
+
|
|
3679
|
+
```typescript
|
|
3680
|
+
async onBeforeScan(): Promise<void>
|
|
3681
|
+
```
|
|
3682
|
+
|
|
3683
|
+
Use this to trigger all registered plugins' pre-scan lifecycle hooks before a documentation scan begins. This allows plugins to perform setup tasks, validate configurations, or prepare resources before any files are processed.
|
|
3684
|
+
|
|
3685
|
+
## Parameters
|
|
3686
|
+
|
|
3687
|
+
This method takes no parameters.
|
|
3688
|
+
|
|
3689
|
+
## Returns
|
|
3690
|
+
|
|
3691
|
+
| Type | Description |
|
|
3692
|
+
|------|-------------|
|
|
3693
|
+
| `Promise<void>` | Resolves when all registered plugins have completed their `onBeforeScan` hooks. Rejects if any plugin hook throws an error. |
|
|
3694
|
+
|
|
3695
|
+
## When to Call
|
|
3696
|
+
|
|
3697
|
+
Call `onBeforeScan()` after `onInit()` but **before** any file scanning or parsing begins. This is the correct place for plugins to:
|
|
3698
|
+
- Set up temporary directories or caches
|
|
3699
|
+
- Validate that required external tools are available
|
|
3700
|
+
- Reset state from a previous scan run
|
|
3701
|
+
- Log scan start events or metrics
|
|
3702
|
+
|
|
3703
|
+
### Example
|
|
3704
|
+
|
|
3705
|
+
```typescript example.ts
|
|
3706
|
+
// Inline types to simulate the PluginManager behavior
|
|
3707
|
+
type HookName = 'onInit' | 'onBeforeScan' | 'onAfterScan'
|
|
3708
|
+
|
|
3709
|
+
interface Plugin {
|
|
3710
|
+
name: string
|
|
3711
|
+
onInit?: () => Promise<void>
|
|
3712
|
+
onBeforeScan?: () => Promise<void>
|
|
3713
|
+
onAfterScan?: <T>(elements: T[]) => Promise<T[]>
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
// Simulated PluginManager class
|
|
3717
|
+
class PluginManager {
|
|
3718
|
+
private plugins: Plugin[] = []
|
|
3719
|
+
|
|
3720
|
+
register(plugin: Plugin): void {
|
|
3721
|
+
this.plugins.push(plugin)
|
|
3722
|
+
console.log(`Plugin registered: ${plugin.name}`)
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
private async runHook(hookName: HookName, ...args: unknown[]): Promise<unknown> {
|
|
3726
|
+
for (const plugin of this.plugins) {
|
|
3727
|
+
const hook = plugin[hookName] as ((...a: unknown[]) => Promise<unknown>) | undefined
|
|
3728
|
+
if (typeof hook === 'function') {
|
|
3729
|
+
await hook.call(plugin, ...args)
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
return undefined
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
async onInit(): Promise<void> {
|
|
3736
|
+
await this.runHook('onInit')
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
async onBeforeScan(): Promise<void> {
|
|
3740
|
+
await this.runHook('onBeforeScan')
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
async onAfterScan<T>(elements: T[]): Promise<T[]> {
|
|
3744
|
+
return ((await this.runHook('onAfterScan', elements)) as T[]) || elements
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
// Example plugins that use the onBeforeScan hook
|
|
3749
|
+
const cachePlugin: Plugin = {
|
|
3750
|
+
name: 'cache-plugin',
|
|
3751
|
+
onBeforeScan: async () => {
|
|
3752
|
+
const cacheDir = process.env.CACHE_DIR || '/tmp/autodocs-cache'
|
|
3753
|
+
console.log(`[cache-plugin] Clearing cache at: ${cacheDir}`)
|
|
3754
|
+
// Simulate async cache clearing
|
|
3755
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
3756
|
+
console.log('[cache-plugin] Cache cleared successfully')
|
|
3757
|
+
},
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
const metricsPlugin: Plugin = {
|
|
3761
|
+
name: 'metrics-plugin',
|
|
3762
|
+
onBeforeScan: async () => {
|
|
3763
|
+
const scanId = `scan_${Date.now()}`
|
|
3764
|
+
console.log(`[metrics-plugin] Starting scan session: ${scanId}`)
|
|
3765
|
+
},
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
async function main() {
|
|
3769
|
+
const manager = new PluginManager()
|
|
3770
|
+
|
|
3771
|
+
manager.register(cachePlugin)
|
|
3772
|
+
manager.register(metricsPlugin)
|
|
3773
|
+
|
|
3774
|
+
try {
|
|
3775
|
+
// Step 1: Initialize all plugins
|
|
3776
|
+
await manager.onInit()
|
|
3777
|
+
console.log('✓ Plugins initialized')
|
|
3778
|
+
|
|
3779
|
+
// Step 2: Run pre-scan hooks before processing any files
|
|
3780
|
+
await manager.onBeforeScan()
|
|
3781
|
+
console.log('✓ Pre-scan hooks completed — safe to begin file scanning')
|
|
3782
|
+
|
|
3783
|
+
// Step 3: (Scanning would happen here)
|
|
3784
|
+
// const files = await scanner.findFiles('./src')
|
|
3785
|
+
// const elements = await parser.parse(files)
|
|
3786
|
+
// const processed = await manager.onAfterScan(elements)
|
|
3787
|
+
} catch (error) {
|
|
3788
|
+
console.error('Pre-scan hook failed — aborting scan:', error)
|
|
3789
|
+
process.exit(1)
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
main()
|
|
3794
|
+
|
|
3795
|
+
// Expected output:
|
|
3796
|
+
// Plugin registered: cache-plugin
|
|
3797
|
+
// Plugin registered: metrics-plugin
|
|
3798
|
+
// ✓ Plugins initialized
|
|
3799
|
+
// [cache-plugin] Clearing cache at: /tmp/autodocs-cache
|
|
3800
|
+
// [cache-plugin] Cache cleared successfully
|
|
3801
|
+
// [metrics-plugin] Starting scan session: scan_1718000000000
|
|
3802
|
+
// ✓ Pre-scan hooks completed — safe to begin file scanning
|
|
3803
|
+
```
|
|
3804
|
+
|
|
3805
|
+
### Related
|
|
3806
|
+
|
|
3807
|
+
<CardGroup cols={3}>
|
|
3808
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
3809
|
+
Uses
|
|
3810
|
+
</Card>
|
|
3811
|
+
<Card title="onInit" icon="link" href="#oninit">
|
|
3812
|
+
Uses
|
|
3813
|
+
</Card>
|
|
3814
|
+
<Card title="onAfterScan" icon="link" href="#onafterscan">
|
|
3815
|
+
Uses
|
|
3816
|
+
</Card>
|
|
3817
|
+
<Card title="onInit" icon="link" href="#oninit">
|
|
3818
|
+
Used by
|
|
3819
|
+
</Card>
|
|
3820
|
+
<Card title="onAfterScan" icon="link" href="#onafterscan">
|
|
3821
|
+
Used by
|
|
3822
|
+
</Card>
|
|
3823
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
3824
|
+
Related
|
|
3825
|
+
</Card>
|
|
3826
|
+
</CardGroup>
|
|
3827
|
+
|
|
3828
|
+
---
|
|
3829
|
+
|
|
3830
|
+
|
|
3831
|
+
## `onBeforeWrite`
|
|
3832
|
+
|
|
3833
|
+
```typescript
|
|
3834
|
+
async onBeforeWrite<T>(docs: T[]): Promise<T[]>
|
|
3835
|
+
```
|
|
3836
|
+
|
|
3837
|
+
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.
|
|
3838
|
+
|
|
3839
|
+
This is the last interception point before output is committed — ideal for plugins that need to inject metadata, reorder entries, strip internal fields, or validate documents.
|
|
3840
|
+
|
|
3841
|
+
## Parameters
|
|
3842
|
+
|
|
3843
|
+
| Name | Type | Required | Description |
|
|
3844
|
+
|------|------|----------|-------------|
|
|
3845
|
+
| `docs` | `T[]` | Yes | Array of documents to be passed through all registered `onBeforeWrite` plugin hooks before writing |
|
|
3846
|
+
|
|
3847
|
+
## Returns
|
|
3848
|
+
|
|
3849
|
+
| Condition | Return Value |
|
|
3850
|
+
|-----------|-------------|
|
|
3851
|
+
| One or more plugins transform the docs | `Promise<T[]>` — the transformed array returned by the last hook in the chain |
|
|
3852
|
+
| No plugins modify the docs | `Promise<T[]>` — the original `docs` array passed in, unchanged |
|
|
3853
|
+
|
|
3854
|
+
The method **always returns an array** — it falls back to the original `docs` if the hook pipeline returns a falsy value.
|
|
3855
|
+
|
|
3856
|
+
### Example
|
|
3857
|
+
|
|
3858
|
+
```typescript example.ts
|
|
3859
|
+
// Inline types to simulate the PluginManager behavior
|
|
3860
|
+
type Plugin<T> = {
|
|
3861
|
+
onBeforeWrite?: (docs: T[]) => Promise<T[]>
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
type DocEntry = {
|
|
3865
|
+
name: string
|
|
3866
|
+
content: string
|
|
3867
|
+
metadata?: Record<string, unknown>
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
// Simulated PluginManager with onBeforeWrite support
|
|
3871
|
+
class PluginManager<T> {
|
|
3872
|
+
private plugins: Plugin<T>[] = []
|
|
3873
|
+
|
|
3874
|
+
register(plugin: Plugin<T>) {
|
|
3875
|
+
this.plugins.push(plugin)
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
private async runHook(hookName: keyof Plugin<T>, data: T[]): Promise<T[] | null> {
|
|
3879
|
+
let result: T[] = data
|
|
3880
|
+
for (const plugin of this.plugins) {
|
|
3881
|
+
const hook = plugin[hookName]
|
|
3882
|
+
if (typeof hook === 'function') {
|
|
3883
|
+
result = await (hook as (docs: T[]) => Promise<T[]>)(result)
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
return result.length > 0 ? result : null
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
async onBeforeWrite(docs: T[]): Promise<T[]> {
|
|
3890
|
+
return (await this.runHook('onBeforeWrite', docs)) || docs
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
// Example plugins that transform docs before writing
|
|
3895
|
+
const timestampPlugin: Plugin<DocEntry> = {
|
|
3896
|
+
onBeforeWrite: async (docs) => {
|
|
3897
|
+
console.log('[timestampPlugin] Injecting write timestamps...')
|
|
3898
|
+
return docs.map((doc) => ({
|
|
3899
|
+
...doc,
|
|
3900
|
+
metadata: {
|
|
3901
|
+
...doc.metadata,
|
|
3902
|
+
writtenAt: new Date().toISOString(),
|
|
3903
|
+
},
|
|
3904
|
+
}))
|
|
3905
|
+
},
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
const filterPlugin: Plugin<DocEntry> = {
|
|
3909
|
+
onBeforeWrite: async (docs) => {
|
|
3910
|
+
console.log('[filterPlugin] Removing draft documents...')
|
|
3911
|
+
return docs.filter((doc) => !doc.name.startsWith('DRAFT_'))
|
|
3912
|
+
},
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
// Setup
|
|
3916
|
+
const manager = new PluginManager<DocEntry>()
|
|
3917
|
+
manager.register(timestampPlugin)
|
|
3918
|
+
manager.register(filterPlugin)
|
|
3919
|
+
|
|
3920
|
+
const rawDocs: DocEntry[] = [
|
|
3921
|
+
{ name: 'getting-started', content: '# Getting Started\n...' },
|
|
3922
|
+
{ name: 'DRAFT_advanced-usage', content: '# Advanced Usage (WIP)' },
|
|
3923
|
+
{ name: 'api-reference', content: '# API Reference\n...' },
|
|
3924
|
+
]
|
|
3925
|
+
|
|
3926
|
+
async function main() {
|
|
3927
|
+
try {
|
|
3928
|
+
console.log('Docs before onBeforeWrite:', rawDocs.map((d) => d.name))
|
|
3929
|
+
|
|
3930
|
+
const finalDocs = await manager.onBeforeWrite(rawDocs)
|
|
3931
|
+
|
|
3932
|
+
console.log('\nDocs after onBeforeWrite:')
|
|
3933
|
+
finalDocs.forEach((doc) => {
|
|
3934
|
+
console.log(` - ${doc.name}`, doc.metadata ? `(writtenAt: ${doc.metadata.writtenAt})` : '')
|
|
3935
|
+
})
|
|
3936
|
+
|
|
3937
|
+
// Expected output:
|
|
3938
|
+
// Docs before onBeforeWrite: ['getting-started', 'DRAFT_advanced-usage', 'api-reference']
|
|
3939
|
+
// [timestampPlugin] Injecting write timestamps...
|
|
3940
|
+
// [filterPlugin] Removing draft documents...
|
|
3941
|
+
// Docs after onBeforeWrite:
|
|
3942
|
+
// - getting-started (writtenAt: 2024-01-15T10:30:00.000Z)
|
|
3943
|
+
// - api-reference (writtenAt: 2024-01-15T10:30:00.000Z)
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
console.error('onBeforeWrite pipeline failed:', error)
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
main()
|
|
3950
|
+
```
|
|
3951
|
+
|
|
3952
|
+
### Related
|
|
3953
|
+
|
|
3954
|
+
<CardGroup cols={3}>
|
|
3955
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
3956
|
+
Uses
|
|
3957
|
+
</Card>
|
|
3958
|
+
<Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
|
|
3959
|
+
Uses
|
|
3960
|
+
</Card>
|
|
3961
|
+
<Card title="onAfterWrite" icon="link" href="#onafterwrite">
|
|
3962
|
+
Uses
|
|
3963
|
+
</Card>
|
|
3964
|
+
<Card title="onAfterGenerate" icon="link" href="#onaftergenerate">
|
|
3965
|
+
Used by
|
|
3966
|
+
</Card>
|
|
3967
|
+
<Card title="onAfterWrite" icon="link" href="#onafterwrite">
|
|
3968
|
+
Used by
|
|
3969
|
+
</Card>
|
|
3970
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
3971
|
+
Related
|
|
3972
|
+
</Card>
|
|
3973
|
+
</CardGroup>
|
|
3974
|
+
|
|
3975
|
+
---
|
|
3976
|
+
|
|
3977
|
+
|
|
3978
|
+
## `onInit`
|
|
3979
|
+
|
|
3980
|
+
```typescript
|
|
3981
|
+
async onInit(): Promise<void>
|
|
3982
|
+
```
|
|
3983
|
+
|
|
3984
|
+
Use this to trigger the initialization lifecycle hook across all registered plugins in a `PluginManager` instance. Call this once after all plugins have been registered and before any scanning or processing begins.
|
|
3985
|
+
|
|
3986
|
+
This method delegates to the internal `runHook('onInit')` mechanism, ensuring every plugin's `onInit` handler is executed in sequence.
|
|
3987
|
+
|
|
3988
|
+
## Parameters
|
|
3989
|
+
|
|
3990
|
+
_None_
|
|
3991
|
+
|
|
3992
|
+
## Returns
|
|
3993
|
+
|
|
3994
|
+
| Type | Description |
|
|
3995
|
+
|------|-------------|
|
|
3996
|
+
| `Promise<void>` | Resolves when all plugin `onInit` hooks have completed. Rejects if any plugin's `onInit` throws an error. |
|
|
3997
|
+
|
|
3998
|
+
## When to Call
|
|
3999
|
+
|
|
4000
|
+
- After instantiating `PluginManager` and registering all plugins
|
|
4001
|
+
- Before calling `onBeforeScan()` or any other lifecycle hooks
|
|
4002
|
+
- Typically called once per application startup
|
|
4003
|
+
|
|
4004
|
+
### Example
|
|
4005
|
+
|
|
4006
|
+
```typescript example.ts
|
|
4007
|
+
// Inline types to simulate the PluginManager lifecycle
|
|
4008
|
+
type HookName = 'onInit' | 'onBeforeScan' | 'onAfterScan'
|
|
4009
|
+
|
|
4010
|
+
type Plugin = {
|
|
4011
|
+
name: string
|
|
4012
|
+
onInit?: () => Promise<void>
|
|
4013
|
+
onBeforeScan?: () => Promise<void>
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
// Simulated PluginManager class (self-contained, no external imports)
|
|
4017
|
+
class PluginManager {
|
|
4018
|
+
private plugins: Plugin[] = []
|
|
4019
|
+
|
|
4020
|
+
register(plugin: Plugin): void {
|
|
4021
|
+
this.plugins.push(plugin)
|
|
4022
|
+
console.log(`Plugin registered: ${plugin.name}`)
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
private async runHook(hookName: HookName): Promise<void> {
|
|
4026
|
+
for (const plugin of this.plugins) {
|
|
4027
|
+
const hook = plugin[hookName]
|
|
4028
|
+
if (typeof hook === 'function') {
|
|
4029
|
+
await hook()
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
async onInit(): Promise<void> {
|
|
4035
|
+
await this.runHook('onInit')
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
async onBeforeScan(): Promise<void> {
|
|
4039
|
+
await this.runHook('onBeforeScan')
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
// Example plugins with onInit handlers
|
|
4044
|
+
const analyticsPlugin: Plugin = {
|
|
4045
|
+
name: 'analytics-plugin',
|
|
4046
|
+
onInit: async () => {
|
|
4047
|
+
const apiKey = process.env.ANALYTICS_API_KEY || 'demo-key-abc123'
|
|
4048
|
+
console.log(`[analytics-plugin] Initialized with key: ${apiKey.slice(0, 8)}...`)
|
|
4049
|
+
},
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
const cachePlugin: Plugin = {
|
|
4053
|
+
name: 'cache-plugin',
|
|
4054
|
+
onInit: async () => {
|
|
4055
|
+
console.log('[cache-plugin] Cache warmed up and ready')
|
|
4056
|
+
},
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
const loggingPlugin: Plugin = {
|
|
4060
|
+
name: 'logging-plugin',
|
|
4061
|
+
// No onInit — should be safely skipped
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
async function main() {
|
|
4065
|
+
const manager = new PluginManager()
|
|
4066
|
+
|
|
4067
|
+
manager.register(analyticsPlugin)
|
|
4068
|
+
manager.register(cachePlugin)
|
|
4069
|
+
manager.register(loggingPlugin)
|
|
4070
|
+
|
|
4071
|
+
console.log('\n--- Running onInit lifecycle hook ---')
|
|
4072
|
+
|
|
4073
|
+
try {
|
|
4074
|
+
await manager.onInit()
|
|
4075
|
+
console.log('\n✓ All plugin onInit hooks completed successfully')
|
|
4076
|
+
// Expected output:
|
|
4077
|
+
// Plugin registered: analytics-plugin
|
|
4078
|
+
// Plugin registered: cache-plugin
|
|
4079
|
+
// Plugin registered: logging-plugin
|
|
4080
|
+
// --- Running onInit lifecycle hook ---
|
|
4081
|
+
// [analytics-plugin] Initialized with key: demo-key...
|
|
4082
|
+
// [cache-plugin] Cache warmed up and ready
|
|
4083
|
+
// ✓ All plugin onInit hooks completed successfully
|
|
4084
|
+
} catch (error) {
|
|
4085
|
+
console.error('✗ Plugin initialization failed:', error)
|
|
4086
|
+
process.exit(1)
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
|
|
4090
|
+
main()
|
|
4091
|
+
```
|
|
4092
|
+
|
|
4093
|
+
### Related
|
|
4094
|
+
|
|
4095
|
+
<CardGroup cols={3}>
|
|
4096
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
4097
|
+
Uses
|
|
4098
|
+
</Card>
|
|
4099
|
+
<Card title="onBeforeScan" icon="link" href="#onbeforescan">
|
|
4100
|
+
Uses
|
|
4101
|
+
</Card>
|
|
4102
|
+
<Card title="onBeforeScan" icon="link" href="#onbeforescan">
|
|
4103
|
+
Used by
|
|
4104
|
+
</Card>
|
|
4105
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
4106
|
+
Related
|
|
4107
|
+
</Card>
|
|
4108
|
+
</CardGroup>
|
|
4109
|
+
|
|
4110
|
+
---
|
|
4111
|
+
|
|
4112
|
+
|
|
4113
|
+
## `register`
|
|
4114
|
+
|
|
4115
|
+
```typescript
|
|
4116
|
+
register(plugin: SkryptPlugin): void
|
|
4117
|
+
```
|
|
4118
|
+
|
|
4119
|
+
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.
|
|
4120
|
+
|
|
4121
|
+
This is the direct registration path — useful when you have a plugin object already constructed in memory and don't need to load it from disk.
|
|
4122
|
+
|
|
4123
|
+
### Parameters
|
|
4124
|
+
|
|
4125
|
+
| Name | Type | Required | Description |
|
|
4126
|
+
|------|------|----------|-------------|
|
|
4127
|
+
| `plugin` | `SkryptPlugin` | ✅ Yes | The plugin object to register. Must have at minimum a `name` property; `version` is optional but will be included in the log output if present. |
|
|
4128
|
+
|
|
4129
|
+
### Returns
|
|
4130
|
+
|
|
4131
|
+
`void` — Pushes the plugin into the internal plugins array and logs a confirmation message to the console in the format:
|
|
4132
|
+
- `Loaded plugin: <name>` (no version)
|
|
4133
|
+
- `Loaded plugin: <name> v<version>` (with version)
|
|
4134
|
+
|
|
4135
|
+
### Behavior Notes
|
|
4136
|
+
- Plugins are stored in insertion order and hooks are run in that order.
|
|
4137
|
+
- There is no deduplication check — registering the same plugin twice will result in it being added twice.
|
|
4138
|
+
- The log output is a side effect of every registration call.
|
|
4139
|
+
|
|
4140
|
+
### Example
|
|
4141
|
+
|
|
4142
|
+
```typescript example.ts
|
|
4143
|
+
// --- Inline types (do not import from autodocs) ---
|
|
4144
|
+
type HookFn = (...args: unknown[]) => unknown | Promise<unknown>
|
|
4145
|
+
|
|
4146
|
+
interface SkryptPlugin {
|
|
4147
|
+
name: string
|
|
4148
|
+
version?: string
|
|
4149
|
+
onBuildStart?: HookFn
|
|
4150
|
+
onBuildEnd?: HookFn
|
|
4151
|
+
transform?: HookFn
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
// --- Inline PluginManager implementation ---
|
|
4155
|
+
class PluginManager {
|
|
4156
|
+
private plugins: SkryptPlugin[] = []
|
|
4157
|
+
|
|
4158
|
+
register(plugin: SkryptPlugin): void {
|
|
4159
|
+
this.plugins.push(plugin)
|
|
4160
|
+
console.log(
|
|
4161
|
+
`Loaded plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`
|
|
4162
|
+
)
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined> {
|
|
4166
|
+
for (const plugin of this.plugins) {
|
|
4167
|
+
const fn = plugin[hook]
|
|
4168
|
+
if (typeof fn === 'function') {
|
|
4169
|
+
const result = await fn(...args)
|
|
4170
|
+
if (result !== undefined) return result as T
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
return undefined
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
getPlugins(): SkryptPlugin[] {
|
|
4177
|
+
return [...this.plugins]
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
// --- Usage example ---
|
|
4182
|
+
const manager = new PluginManager()
|
|
4183
|
+
|
|
4184
|
+
// Plugin with a version
|
|
4185
|
+
const sassPlugin: SkryptPlugin = {
|
|
4186
|
+
name: 'sass-compiler',
|
|
4187
|
+
version: '2.1.0',
|
|
4188
|
+
onBuildStart: async () => {
|
|
4189
|
+
console.log(' [sass-compiler] Compiling stylesheets...')
|
|
4190
|
+
},
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
// Plugin without a version
|
|
4194
|
+
const envPlugin: SkryptPlugin = {
|
|
4195
|
+
name: 'env-injector',
|
|
4196
|
+
onBuildStart: async () => {
|
|
4197
|
+
console.log(' [env-injector] Injecting environment variables...')
|
|
4198
|
+
},
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
async function main() {
|
|
4202
|
+
try {
|
|
4203
|
+
manager.register(sassPlugin)
|
|
4204
|
+
// Output: Loaded plugin: sass-compiler v2.1.0
|
|
4205
|
+
|
|
4206
|
+
manager.register(envPlugin)
|
|
4207
|
+
// Output: Loaded plugin: env-injector
|
|
4208
|
+
|
|
4209
|
+
console.log('\nRegistered plugins:', manager.getPlugins().map(p => p.name))
|
|
4210
|
+
// Output: Registered plugins: [ 'sass-compiler', 'env-injector' ]
|
|
4211
|
+
|
|
4212
|
+
console.log('\nRunning onBuildStart hooks...')
|
|
4213
|
+
await manager.runHook('onBuildStart')
|
|
4214
|
+
// Output:
|
|
4215
|
+
// [sass-compiler] Compiling stylesheets...
|
|
4216
|
+
// [env-injector] Injecting environment variables...
|
|
4217
|
+
} catch (error) {
|
|
4218
|
+
console.error('Plugin registration failed:', error)
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
main()
|
|
4223
|
+
```
|
|
4224
|
+
|
|
4225
|
+
### Related
|
|
4226
|
+
|
|
4227
|
+
<CardGroup cols={3}>
|
|
4228
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
4229
|
+
Uses
|
|
4230
|
+
</Card>
|
|
4231
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
4232
|
+
Used by
|
|
4233
|
+
</Card>
|
|
4234
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
4235
|
+
Related
|
|
4236
|
+
</Card>
|
|
4237
|
+
</CardGroup>
|
|
4238
|
+
|
|
4239
|
+
---
|
|
4240
|
+
|
|
4241
|
+
|
|
4242
|
+
## `runHook`
|
|
4243
|
+
|
|
4244
|
+
```typescript
|
|
4245
|
+
async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined>
|
|
4246
|
+
```
|
|
4247
|
+
|
|
4248
|
+
Use this to execute a named hook across all registered plugins in sequence, passing data through each plugin's implementation of that hook.
|
|
4249
|
+
|
|
4250
|
+
`runHook` iterates over every registered plugin, finds those that implement the specified hook method, and calls them in registration order. Each plugin receives the result of the previous plugin, enabling a pipeline/middleware pattern where plugins can transform data as it flows through.
|
|
4251
|
+
|
|
4252
|
+
## Parameters
|
|
4253
|
+
|
|
4254
|
+
| Name | Type | Required | Description |
|
|
4255
|
+
|------|------|----------|-------------|
|
|
4256
|
+
| `hook` | `keyof SkryptPlugin` | Yes | The name of the plugin lifecycle method to invoke (e.g., `'beforeCompile'`, `'afterCompile'`) |
|
|
4257
|
+
| `...args` | `unknown[]` | No | Arguments passed to the hook. The first argument (`args[0]`) is used as the initial result value and passed through the plugin chain |
|
|
4258
|
+
|
|
4259
|
+
## Returns
|
|
4260
|
+
|
|
4261
|
+
| Condition | Return Value |
|
|
4262
|
+
|-----------|-------------|
|
|
4263
|
+
| One or more plugins implement the hook | `Promise<T>` — the final transformed value after all plugins have processed it |
|
|
4264
|
+
| No plugins implement the hook | `Promise<undefined>` — resolves with the initial `args[0]` cast as `T`, or `undefined` if no args provided |
|
|
4265
|
+
|
|
4266
|
+
The return type `T` is inferred from the generic parameter you provide at the call site.
|
|
4267
|
+
|
|
4268
|
+
### Example
|
|
4269
|
+
|
|
4270
|
+
```typescript example.ts
|
|
4271
|
+
// --- Inline types (do not import from autodocs) ---
|
|
4272
|
+
interface SkryptPlugin {
|
|
4273
|
+
name: string
|
|
4274
|
+
version?: string
|
|
4275
|
+
beforeCompile?: (source: string) => Promise<string> | string
|
|
4276
|
+
afterCompile?: (output: string) => Promise<string> | string
|
|
4277
|
+
onError?: (error: Error) => Promise<void> | void
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
// --- Inline PluginManager implementation ---
|
|
4281
|
+
class PluginManager {
|
|
4282
|
+
private plugins: SkryptPlugin[] = []
|
|
4283
|
+
|
|
4284
|
+
register(plugin: SkryptPlugin): void {
|
|
4285
|
+
this.plugins.push(plugin)
|
|
4286
|
+
console.log(
|
|
4287
|
+
`Loaded plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`
|
|
4288
|
+
)
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
async runHook<T>(hook: keyof SkryptPlugin, ...args: unknown[]): Promise<T | undefined> {
|
|
4292
|
+
let result = args[0] as T
|
|
4293
|
+
|
|
4294
|
+
for (const plugin of this.plugins) {
|
|
4295
|
+
const fn = plugin[hook] as ((...args: unknown[]) => Promise<T> | T) | undefined
|
|
4296
|
+
if (typeof fn === 'function') {
|
|
4297
|
+
result = await fn.call(plugin, result)
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
return result
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
// --- Example plugins ---
|
|
4306
|
+
const stripCommentsPlugin: SkryptPlugin = {
|
|
4307
|
+
name: 'strip-comments',
|
|
4308
|
+
version: '1.0.0',
|
|
4309
|
+
beforeCompile(source: string) {
|
|
4310
|
+
console.log('[strip-comments] Removing comments...')
|
|
4311
|
+
return source.replace(/\/\/.*$/gm, '').trim()
|
|
4312
|
+
},
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
const minifyPlugin: SkryptPlugin = {
|
|
4316
|
+
name: 'minify',
|
|
4317
|
+
version: '2.1.0',
|
|
4318
|
+
beforeCompile(source: string) {
|
|
4319
|
+
console.log('[minify] Minifying source...')
|
|
4320
|
+
return source.replace(/\s+/g, ' ').trim()
|
|
4321
|
+
},
|
|
4322
|
+
afterCompile(output: string) {
|
|
4323
|
+
console.log('[minify] Post-processing output...')
|
|
4324
|
+
return `/* minified */\n${output}`
|
|
4325
|
+
},
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
const bannerPlugin: SkryptPlugin = {
|
|
4329
|
+
name: 'banner',
|
|
4330
|
+
afterCompile(output: string) {
|
|
4331
|
+
console.log('[banner] Injecting banner...')
|
|
4332
|
+
return `// Built with Skrypt\n${output}`
|
|
4333
|
+
},
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
// --- Run the example ---
|
|
4337
|
+
async function main() {
|
|
4338
|
+
const manager = new PluginManager()
|
|
4339
|
+
|
|
4340
|
+
manager.register(stripCommentsPlugin)
|
|
4341
|
+
manager.register(minifyPlugin)
|
|
4342
|
+
manager.register(bannerPlugin)
|
|
4343
|
+
|
|
4344
|
+
const rawSource = `
|
|
4345
|
+
// This is a comment
|
|
4346
|
+
let x = 1
|
|
4347
|
+
let y = 2
|
|
4348
|
+
console.log(x + y)
|
|
4349
|
+
`
|
|
4350
|
+
|
|
4351
|
+
try {
|
|
4352
|
+
// Run the beforeCompile hook — source flows through each plugin in order
|
|
4353
|
+
const processedSource = await manager.runHook<string>('beforeCompile', rawSource)
|
|
4354
|
+
console.log('\n--- Processed Source ---')
|
|
4355
|
+
console.log(processedSource)
|
|
4356
|
+
// Output: "let x = 1 let y = 2 console.log(x + y)"
|
|
4357
|
+
|
|
4358
|
+
// Run the afterCompile hook — only minify and banner plugins implement it
|
|
4359
|
+
const finalOutput = await manager.runHook<string>('afterCompile', processedSource)
|
|
4360
|
+
console.log('\n--- Final Output ---')
|
|
4361
|
+
console.log(finalOutput)
|
|
4362
|
+
// Output:
|
|
4363
|
+
// // Built with Skrypt
|
|
4364
|
+
// /* minified */
|
|
4365
|
+
// let x = 1 let y = 2 console.log(x + y)
|
|
4366
|
+
|
|
4367
|
+
// Run a hook with no implementations — returns the initial value unchanged
|
|
4368
|
+
const unchanged = await manager.runHook<string>('onError', 'no-op value')
|
|
4369
|
+
console.log('\n--- Hook with no matching implementations ---')
|
|
4370
|
+
console.log(unchanged) // Output: no-op value (onError expects void, shown for demonstration)
|
|
4371
|
+
} catch (error) {
|
|
4372
|
+
console.error('Hook execution failed:', error)
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
main()
|
|
4377
|
+
```
|
|
4378
|
+
|
|
4379
|
+
### Related
|
|
4380
|
+
|
|
4381
|
+
<CardGroup cols={3}>
|
|
4382
|
+
<Card title="register" icon="link" href="#register">
|
|
4383
|
+
Uses
|
|
4384
|
+
</Card>
|
|
4385
|
+
<Card title="register" icon="link" href="#register">
|
|
4386
|
+
Used by
|
|
4387
|
+
</Card>
|
|
4388
|
+
<Card title="onInit" icon="link" href="#oninit">
|
|
4389
|
+
Used by
|
|
4390
|
+
</Card>
|
|
4391
|
+
<Card title="onBeforeScan" icon="link" href="#onbeforescan">
|
|
4392
|
+
Used by
|
|
4393
|
+
</Card>
|
|
4394
|
+
<Card title="onAfterScan" icon="link" href="#onafterscan">
|
|
4395
|
+
Used by
|
|
4396
|
+
</Card>
|
|
4397
|
+
<Card title="onBeforeGenerate" icon="link" href="#onbeforegenerate">
|
|
4398
|
+
Used by
|
|
4399
|
+
</Card>
|
|
4400
|
+
</CardGroup>
|
|
4401
|
+
|
|
4402
|
+
---
|
|
4403
|
+
|
|
4404
|
+
|
|
4405
|
+
## `transformContent`
|
|
4406
|
+
|
|
4407
|
+
```typescript
|
|
4408
|
+
async transformContent(content: string, filePath: string): Promise<string>
|
|
4409
|
+
```
|
|
4410
|
+
|
|
4411
|
+
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. Ideal for applying transformations like syntax highlighting, markdown processing, code formatting, or custom replacements across your documentation pipeline.
|
|
4412
|
+
|
|
4413
|
+
Each plugin in the manager that implements `transformContent` is called in sequence — the output of one becomes the input of the next.
|
|
4414
|
+
|
|
4415
|
+
## Parameters
|
|
4416
|
+
|
|
4417
|
+
| Name | Type | Required | Description |
|
|
4418
|
+
|------|------|----------|-------------|
|
|
4419
|
+
| `content` | `string` | ✅ | The raw file content to be transformed |
|
|
4420
|
+
| `filePath` | `string` | ✅ | The path of the file being processed — plugins can use this to apply conditional logic based on file type or location |
|
|
4421
|
+
|
|
4422
|
+
## Returns
|
|
4423
|
+
|
|
4424
|
+
| Condition | Return Value |
|
|
4425
|
+
|-----------|-------------|
|
|
4426
|
+
| All plugins succeed | `Promise<string>` — the fully transformed content after passing through every plugin in the chain |
|
|
4427
|
+
| A plugin throws | The error propagates — content from that point forward is not processed |
|
|
4428
|
+
| No plugins implement `transformContent` | `Promise<string>` — original content returned unchanged |
|
|
4429
|
+
|
|
4430
|
+
### Example
|
|
4431
|
+
|
|
4432
|
+
```typescript example.ts
|
|
4433
|
+
// Inline types — no external imports needed
|
|
4434
|
+
type TransformPlugin = {
|
|
4435
|
+
name: string
|
|
4436
|
+
transformContent?: (content: string, filePath: string) => Promise<string>
|
|
4437
|
+
}
|
|
4438
|
+
|
|
4439
|
+
// Simulated PluginManager class
|
|
4440
|
+
class PluginManager {
|
|
4441
|
+
private plugins: TransformPlugin[]
|
|
4442
|
+
|
|
4443
|
+
constructor(plugins: TransformPlugin[]) {
|
|
4444
|
+
this.plugins = plugins
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
async transformContent(content: string, filePath: string): Promise<string> {
|
|
4448
|
+
let result = content
|
|
4449
|
+
|
|
4450
|
+
for (const plugin of this.plugins) {
|
|
4451
|
+
if (typeof plugin.transformContent === 'function') {
|
|
4452
|
+
try {
|
|
4453
|
+
result = await plugin.transformContent(result, filePath)
|
|
4454
|
+
console.log(`[${plugin.name}] transformation applied`)
|
|
4455
|
+
} catch (error) {
|
|
4456
|
+
console.error(`[${plugin.name}] failed to transform content:`, error)
|
|
4457
|
+
throw error
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4462
|
+
return result
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
// Example plugins — each transforms content in sequence
|
|
4467
|
+
const markdownHeaderPlugin: TransformPlugin = {
|
|
4468
|
+
name: 'markdown-header',
|
|
4469
|
+
transformContent: async (content, filePath) => {
|
|
4470
|
+
if (!filePath.endsWith('.md')) return content
|
|
4471
|
+
const timestamp = new Date().toISOString()
|
|
4472
|
+
return `<!-- Generated: ${timestamp} -->\n${content}`
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
const codeFencePlugin: TransformPlugin = {
|
|
4477
|
+
name: 'code-fence-formatter',
|
|
4478
|
+
transformContent: async (content, _filePath) => {
|
|
4479
|
+
// Normalize code fences: replace ``` with properly spaced versions
|
|
4480
|
+
return content.replace(/```(\w+)/g, '```$1\n').trimEnd()
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
const noOpPlugin: TransformPlugin = {
|
|
4485
|
+
name: 'logger-only'
|
|
4486
|
+
// No transformContent — will be skipped automatically
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
async function main() {
|
|
4490
|
+
try {
|
|
4491
|
+
const manager = new PluginManager([
|
|
4492
|
+
markdownHeaderPlugin,
|
|
4493
|
+
codeFencePlugin,
|
|
4494
|
+
noOpPlugin
|
|
4495
|
+
])
|
|
4496
|
+
|
|
4497
|
+
const rawContent = `# My Docs\n\nHere is some code:\n\`\`\`typescript\nconst x = 1\n\`\`\``
|
|
4498
|
+
const filePath = '/docs/guide/getting-started.md'
|
|
4499
|
+
|
|
4500
|
+
console.log('--- Input ---')
|
|
4501
|
+
console.log(rawContent)
|
|
4502
|
+
console.log('\n--- Transforming... ---')
|
|
4503
|
+
|
|
4504
|
+
const transformed = await manager.transformContent(rawContent, filePath)
|
|
4505
|
+
|
|
4506
|
+
console.log('\n--- Output ---')
|
|
4507
|
+
console.log(transformed)
|
|
4508
|
+
// Expected output:
|
|
4509
|
+
// <!-- Generated: 2024-01-15T10:30:00.000Z -->
|
|
4510
|
+
// # My Docs
|
|
4511
|
+
//
|
|
4512
|
+
// Here is some code:
|
|
4513
|
+
// ```typescript
|
|
4514
|
+
//
|
|
4515
|
+
// const x = 1
|
|
4516
|
+
// ```
|
|
4517
|
+
} catch (error) {
|
|
4518
|
+
console.error('Transformation pipeline failed:', error)
|
|
4519
|
+
process.exit(1)
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
main()
|
|
4524
|
+
```
|
|
4525
|
+
|
|
4526
|
+
### Related
|
|
4527
|
+
|
|
4528
|
+
<CardGroup cols={3}>
|
|
4529
|
+
<Card title="runHook" icon="link" href="#runhook">
|
|
4530
|
+
Uses
|
|
4531
|
+
</Card>
|
|
4532
|
+
<Card title="onAfterWrite" icon="link" href="#onafterwrite">
|
|
4533
|
+
Uses
|
|
4534
|
+
</Card>
|
|
4535
|
+
<Card title="onAfterWrite" icon="link" href="#onafterwrite">
|
|
4536
|
+
Used by
|
|
4537
|
+
</Card>
|
|
4538
|
+
<Card title="PluginManager" icon="link" href="#pluginmanager">
|
|
4539
|
+
Related
|
|
4540
|
+
</Card>
|
|
4541
|
+
</CardGroup>
|
|
4542
|
+
|
|
4543
|
+
---
|
|
4544
|
+
|