@kubb/core 5.0.0-alpha.29 → 5.0.0-alpha.30
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/{PluginDriver-C6VX0skO.d.ts → PluginDriver-D110FoJ-.d.ts} +265 -108
- package/dist/hooks.d.ts +1 -1
- package/dist/index.cjs +495 -529
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -80
- package/dist/index.js +496 -525
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/PluginDriver.ts +44 -10
- package/src/build.ts +87 -8
- package/src/createPlugin.ts +3 -1
- package/src/defineGenerator.ts +101 -134
- package/src/index.ts +1 -2
- package/src/renderNode.tsx +14 -186
- package/src/types.ts +213 -11
- package/src/utils/executeStrategies.ts +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/core",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.30",
|
|
4
4
|
"description": "Core functionality for Kubb's plugin-based code generation system, providing the foundation for transforming OpenAPI specifications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"remeda": "^2.33.7",
|
|
72
72
|
"semver": "^7.7.4",
|
|
73
73
|
"tinyexec": "^1.0.4",
|
|
74
|
-
"@kubb/ast": "5.0.0-alpha.
|
|
74
|
+
"@kubb/ast": "5.0.0-alpha.30"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@types/semver": "^7.7.1",
|
package/src/PluginDriver.ts
CHANGED
|
@@ -101,7 +101,13 @@ export class PluginDriver {
|
|
|
101
101
|
this.config = config
|
|
102
102
|
this.options = options
|
|
103
103
|
config.plugins
|
|
104
|
-
.map((plugin) => Object.assign({
|
|
104
|
+
.map((plugin) => Object.assign({ buildStart() {}, buildEnd() {} }, plugin) as unknown as Plugin)
|
|
105
|
+
.filter((plugin) => {
|
|
106
|
+
if (typeof plugin.apply === 'function') {
|
|
107
|
+
return plugin.apply(config)
|
|
108
|
+
}
|
|
109
|
+
return true
|
|
110
|
+
})
|
|
105
111
|
.sort((a, b) => {
|
|
106
112
|
if (b.pre?.includes(a.name)) return 1
|
|
107
113
|
if (b.post?.includes(a.name)) return -1
|
|
@@ -122,9 +128,16 @@ export class PluginDriver {
|
|
|
122
128
|
const baseContext = {
|
|
123
129
|
fabric: driver.options.fabric,
|
|
124
130
|
config: driver.config,
|
|
131
|
+
get root(): string {
|
|
132
|
+
return resolve(driver.config.root, driver.config.output.path)
|
|
133
|
+
},
|
|
134
|
+
getMode(output: { path: string }): FabricFile.Mode {
|
|
135
|
+
return getMode(resolve(driver.config.root, driver.config.output.path, output.path))
|
|
136
|
+
},
|
|
137
|
+
events: driver.options.events,
|
|
125
138
|
plugin,
|
|
126
139
|
getPlugin: driver.getPlugin.bind(driver),
|
|
127
|
-
|
|
140
|
+
requirePlugin: driver.requirePlugin.bind(driver),
|
|
128
141
|
driver: driver,
|
|
129
142
|
addFile: async (...files: Array<FabricFile.File>) => {
|
|
130
143
|
await this.options.fabric.addFile(...files)
|
|
@@ -144,6 +157,15 @@ export class PluginDriver {
|
|
|
144
157
|
get transformer() {
|
|
145
158
|
return plugin.transformer
|
|
146
159
|
},
|
|
160
|
+
warn(message: string) {
|
|
161
|
+
driver.events.emit('warn', message)
|
|
162
|
+
},
|
|
163
|
+
error(error: string | Error) {
|
|
164
|
+
driver.events.emit('error', typeof error === 'string' ? new Error(error) : error)
|
|
165
|
+
},
|
|
166
|
+
info(message: string) {
|
|
167
|
+
driver.events.emit('info', message)
|
|
168
|
+
},
|
|
147
169
|
openInStudio(options?: DevtoolsOptions) {
|
|
148
170
|
if (!driver.config.devtools || driver.#studioIsOpen) {
|
|
149
171
|
return
|
|
@@ -167,12 +189,9 @@ export class PluginDriver {
|
|
|
167
189
|
|
|
168
190
|
const mergedExtras: Record<string, unknown> = {}
|
|
169
191
|
|
|
170
|
-
for (const
|
|
171
|
-
if (typeof
|
|
172
|
-
const result = (
|
|
173
|
-
baseContext as unknown as PluginContext,
|
|
174
|
-
baseContext as unknown as PluginContext,
|
|
175
|
-
)
|
|
192
|
+
for (const p of this.plugins.values()) {
|
|
193
|
+
if (typeof p.inject === 'function') {
|
|
194
|
+
const result = (p.inject as (this: PluginContext) => unknown).call(baseContext as unknown as PluginContext)
|
|
176
195
|
if (result !== null && typeof result === 'object') {
|
|
177
196
|
Object.assign(mergedExtras, result)
|
|
178
197
|
}
|
|
@@ -482,8 +501,23 @@ export class PluginDriver {
|
|
|
482
501
|
this.events.emit('plugins:hook:progress:end', { hookName })
|
|
483
502
|
}
|
|
484
503
|
|
|
485
|
-
getPlugin<
|
|
486
|
-
|
|
504
|
+
getPlugin<TName extends keyof Kubb.PluginRegistry>(pluginName: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
|
|
505
|
+
getPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(pluginName: string): Plugin<TOptions> | undefined
|
|
506
|
+
getPlugin(pluginName: string): Plugin | undefined {
|
|
507
|
+
return this.plugins.get(pluginName) as Plugin | undefined
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Like `getPlugin` but throws a descriptive error when the plugin is not found.
|
|
512
|
+
*/
|
|
513
|
+
requirePlugin<TName extends keyof Kubb.PluginRegistry>(pluginName: TName): Plugin<Kubb.PluginRegistry[TName]>
|
|
514
|
+
requirePlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(pluginName: string): Plugin<TOptions>
|
|
515
|
+
requirePlugin(pluginName: string): Plugin {
|
|
516
|
+
const plugin = this.plugins.get(pluginName)
|
|
517
|
+
if (!plugin) {
|
|
518
|
+
throw new Error(`[kubb] Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`)
|
|
519
|
+
}
|
|
520
|
+
return plugin
|
|
487
521
|
}
|
|
488
522
|
|
|
489
523
|
/**
|
package/src/build.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { dirname, relative, resolve } from 'node:path'
|
|
2
2
|
import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, getRelativePath, URLPath } from '@internals/utils'
|
|
3
|
+
import { transform, walk } from '@kubb/ast'
|
|
4
|
+
import type { OperationNode } from '@kubb/ast/types'
|
|
3
5
|
import type { FabricFile, Fabric as FabricType } from '@kubb/fabric-core/types'
|
|
4
6
|
import { createFabric } from '@kubb/react-fabric'
|
|
5
7
|
import { typescriptParser } from '@kubb/react-fabric/parsers'
|
|
@@ -7,10 +9,12 @@ import { fsPlugin } from '@kubb/react-fabric/plugins'
|
|
|
7
9
|
import { isInputPath } from './config.ts'
|
|
8
10
|
import { BARREL_FILENAME, DEFAULT_BANNER, DEFAULT_CONCURRENCY, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL } from './constants.ts'
|
|
9
11
|
import { PluginDriver } from './PluginDriver.ts'
|
|
12
|
+
import { applyHookResult } from './renderNode.tsx'
|
|
10
13
|
import { fsStorage } from './storages/fsStorage.ts'
|
|
11
|
-
import type { AdapterSource, Config, KubbEvents,
|
|
14
|
+
import type { AdapterSource, Config, KubbEvents, Plugin, PluginContext, Storage, UserConfig } from './types.ts'
|
|
12
15
|
import { getDiagnosticInfo } from './utils/diagnostics.ts'
|
|
13
16
|
import type { FileMetaBase } from './utils/getBarrelFiles.ts'
|
|
17
|
+
import { getBarrelFiles } from './utils/getBarrelFiles.ts'
|
|
14
18
|
|
|
15
19
|
type BuildOptions = {
|
|
16
20
|
config: UserConfig
|
|
@@ -255,6 +259,57 @@ export async function build(options: BuildOptions, overrides?: SetupResult): Pro
|
|
|
255
259
|
}
|
|
256
260
|
}
|
|
257
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
264
|
+
* (`schema`, `operation`, `operations`).
|
|
265
|
+
*
|
|
266
|
+
* - Each hook accepts a single handler **or an array** — all entries are called in sequence.
|
|
267
|
+
* - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
|
|
268
|
+
* - Return values are handled via `applyHookResult`: React elements are rendered,
|
|
269
|
+
* `FabricFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
|
|
270
|
+
* - Barrel files are generated automatically when `output.barrelType` is set.
|
|
271
|
+
*/
|
|
272
|
+
async function runPluginAstHooks(plugin: Plugin, context: PluginContext): Promise<void> {
|
|
273
|
+
const { adapter, rootNode, resolver, fabric } = context
|
|
274
|
+
const { exclude, include, override } = plugin.options
|
|
275
|
+
|
|
276
|
+
if (!adapter || !rootNode) {
|
|
277
|
+
throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const collectedOperations: Array<OperationNode> = []
|
|
281
|
+
|
|
282
|
+
await walk(rootNode, {
|
|
283
|
+
depth: 'shallow',
|
|
284
|
+
async schema(node) {
|
|
285
|
+
if (!plugin.schema) return
|
|
286
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
287
|
+
const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
288
|
+
if (options === null) return
|
|
289
|
+
const result = await plugin.schema.call(context, transformedNode, options)
|
|
290
|
+
|
|
291
|
+
await applyHookResult(result, fabric)
|
|
292
|
+
},
|
|
293
|
+
async operation(node) {
|
|
294
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
295
|
+
const options = resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
296
|
+
if (options !== null) {
|
|
297
|
+
collectedOperations.push(transformedNode)
|
|
298
|
+
if (plugin.operation) {
|
|
299
|
+
const result = await plugin.operation.call(context, transformedNode, options)
|
|
300
|
+
await applyHookResult(result, fabric)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
if (plugin.operations && collectedOperations.length > 0) {
|
|
307
|
+
const result = await plugin.operations.call(context, collectedOperations, plugin.options)
|
|
308
|
+
|
|
309
|
+
await applyHookResult(result, fabric)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
258
313
|
/**
|
|
259
314
|
* Runs a full Kubb build and captures errors instead of throwing.
|
|
260
315
|
*
|
|
@@ -277,8 +332,8 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
277
332
|
for (const plugin of driver.plugins.values()) {
|
|
278
333
|
const context = driver.getContext(plugin)
|
|
279
334
|
const hrStart = process.hrtime()
|
|
280
|
-
|
|
281
|
-
const
|
|
335
|
+
const { output } = plugin.options ?? {}
|
|
336
|
+
const root = resolve(config.root, config.output.path)
|
|
282
337
|
|
|
283
338
|
try {
|
|
284
339
|
const timestamp = new Date()
|
|
@@ -287,10 +342,26 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
287
342
|
|
|
288
343
|
await events.emit('debug', {
|
|
289
344
|
date: timestamp,
|
|
290
|
-
logs: ['
|
|
345
|
+
logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
|
|
291
346
|
})
|
|
292
347
|
|
|
293
|
-
|
|
348
|
+
// Call buildStart() for any custom plugin logic
|
|
349
|
+
await plugin.buildStart.call(context)
|
|
350
|
+
|
|
351
|
+
// Dispatch schema/operation/operations hooks (direct hooks or composed via composeGenerators)
|
|
352
|
+
if (plugin.schema || plugin.operation || plugin.operations) {
|
|
353
|
+
await runPluginAstHooks(plugin, context)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (output) {
|
|
357
|
+
const barrelFiles = await getBarrelFiles(fabric.files, {
|
|
358
|
+
type: output.barrelType ?? 'named',
|
|
359
|
+
root,
|
|
360
|
+
output,
|
|
361
|
+
meta: { pluginName: plugin.name },
|
|
362
|
+
})
|
|
363
|
+
await context.upsertFile(...barrelFiles)
|
|
364
|
+
}
|
|
294
365
|
|
|
295
366
|
const duration = getElapsedMs(hrStart)
|
|
296
367
|
pluginTimings.set(plugin.name, duration)
|
|
@@ -299,7 +370,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
299
370
|
|
|
300
371
|
await events.emit('debug', {
|
|
301
372
|
date: new Date(),
|
|
302
|
-
logs: [`✓ Plugin
|
|
373
|
+
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
303
374
|
})
|
|
304
375
|
} catch (caughtError) {
|
|
305
376
|
const error = caughtError as Error
|
|
@@ -315,7 +386,7 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
315
386
|
await events.emit('debug', {
|
|
316
387
|
date: errorTimestamp,
|
|
317
388
|
logs: [
|
|
318
|
-
'✗ Plugin
|
|
389
|
+
'✗ Plugin start failed',
|
|
319
390
|
` • Plugin Name: ${plugin.name}`,
|
|
320
391
|
` • Error: ${error.constructor.name} - ${error.message}`,
|
|
321
392
|
' • Stack Trace:',
|
|
@@ -372,6 +443,14 @@ export async function safeBuild(options: BuildOptions, overrides?: SetupResult):
|
|
|
372
443
|
|
|
373
444
|
await fabric.write({ extension: config.output.extension })
|
|
374
445
|
|
|
446
|
+
// Call buildEnd() on each plugin after all files are written
|
|
447
|
+
for (const plugin of driver.plugins.values()) {
|
|
448
|
+
if (plugin.buildEnd) {
|
|
449
|
+
const context = driver.getContext(plugin)
|
|
450
|
+
await plugin.buildEnd.call(context)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
375
454
|
return {
|
|
376
455
|
failedPlugins,
|
|
377
456
|
fabric,
|
|
@@ -417,7 +496,7 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
|
|
|
417
496
|
|
|
418
497
|
const meta = file.meta as FileMetaBase | undefined
|
|
419
498
|
const plugin = meta?.pluginName ? pluginNameMap.get(meta.pluginName) : undefined
|
|
420
|
-
const pluginOptions = plugin?.options
|
|
499
|
+
const pluginOptions = plugin?.options
|
|
421
500
|
|
|
422
501
|
if (!pluginOptions || pluginOptions.output?.barrelType === false) {
|
|
423
502
|
return []
|
package/src/createPlugin.ts
CHANGED
|
@@ -9,10 +9,11 @@ type PluginBuilder<T extends PluginFactoryOptions = PluginFactoryOptions> = (opt
|
|
|
9
9
|
* Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
|
+
* ```ts
|
|
12
13
|
* export const myPlugin = createPlugin<MyPlugin>((options) => {
|
|
13
14
|
* return {
|
|
14
15
|
* name: 'my-plugin',
|
|
15
|
-
* options,
|
|
16
|
+
* get options() { return options },
|
|
16
17
|
* resolvePath(baseName) { ... },
|
|
17
18
|
* resolveName(name, type) { ... },
|
|
18
19
|
* }
|
|
@@ -20,6 +21,7 @@ type PluginBuilder<T extends PluginFactoryOptions = PluginFactoryOptions> = (opt
|
|
|
20
21
|
*
|
|
21
22
|
* // instantiate
|
|
22
23
|
* const plugin = myPlugin({ output: { path: 'src/gen' } })
|
|
24
|
+
* ```
|
|
23
25
|
*/
|
|
24
26
|
export function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(
|
|
25
27
|
build: PluginBuilder<T>,
|
package/src/defineGenerator.ts
CHANGED
|
@@ -1,159 +1,126 @@
|
|
|
1
|
+
import type { PossiblePromise } from '@internals/utils'
|
|
1
2
|
import type { OperationNode, SchemaNode } from '@kubb/ast/types'
|
|
2
3
|
import type { FabricFile } from '@kubb/fabric-core/types'
|
|
3
4
|
import type { FabricReactNode } from '@kubb/react-fabric/types'
|
|
4
|
-
import
|
|
5
|
-
import type {
|
|
5
|
+
import { applyHookResult } from './renderNode.tsx'
|
|
6
|
+
import type { GeneratorContext, PluginFactoryOptions } from './types.ts'
|
|
6
7
|
|
|
7
|
-
export type
|
|
8
|
+
export type { GeneratorContext } from './types.ts'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
node: OperationNode
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Props for the `schema` lifecycle — receives a single schema node.
|
|
37
|
-
*/
|
|
38
|
-
export type SchemaV2Props<TPlugin extends PluginFactoryOptions = PluginFactoryOptions> = {
|
|
39
|
-
config: Config
|
|
40
|
-
adapter: Adapter
|
|
41
|
-
driver: PluginDriver
|
|
42
|
-
plugin: Plugin<TPlugin>
|
|
43
|
-
options: Plugin<TPlugin>['options']
|
|
44
|
-
resolver: TPlugin['resolver']
|
|
45
|
-
node: SchemaNode
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Input shape for a core v2 async generator — lifecycle methods are optional.
|
|
50
|
-
*/
|
|
51
|
-
type UserCoreGeneratorV2<TPlugin extends PluginFactoryOptions> = {
|
|
52
|
-
name: string
|
|
53
|
-
type: 'core'
|
|
54
|
-
version?: '2'
|
|
55
|
-
operations?(props: OperationsV2Props<TPlugin>): Promise<Array<FabricFile.File>>
|
|
56
|
-
operation?(props: OperationV2Props<TPlugin>): Promise<Array<FabricFile.File>>
|
|
57
|
-
schema?(props: SchemaV2Props<TPlugin>): Promise<Array<FabricFile.File>>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Input shape for a React v2 generator — component methods are optional.
|
|
62
|
-
*/
|
|
63
|
-
type UserReactGeneratorV2<TPlugin extends PluginFactoryOptions> = {
|
|
64
|
-
name: string
|
|
65
|
-
type: 'react'
|
|
66
|
-
version?: '2'
|
|
67
|
-
Operations?(props: OperationsV2Props<TPlugin>): FabricReactNode
|
|
68
|
-
Operation?(props: OperationV2Props<TPlugin>): FabricReactNode
|
|
69
|
-
Schema?(props: SchemaV2Props<TPlugin>): FabricReactNode
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* A fully resolved core v2 generator with `version: '2'` and guaranteed async lifecycle methods.
|
|
11
|
+
* A generator is a named object with optional `schema`, `operation`, and `operations`
|
|
12
|
+
* methods. Each method is called with `this = PluginContext` of the parent plugin,
|
|
13
|
+
* giving full access to `this.config`, `this.resolver`, `this.adapter`, `this.fabric`,
|
|
14
|
+
* `this.driver`, etc.
|
|
15
|
+
*
|
|
16
|
+
* Return a React element, an array of `FabricFile.File`, or `void` to handle file
|
|
17
|
+
* writing manually via `this.upsertFile`. Both React and core (non-React) generators
|
|
18
|
+
* use the same method signatures — the return type determines how output is handled.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* export const typeGenerator = defineGenerator<PluginTs>({
|
|
23
|
+
* name: 'typescript',
|
|
24
|
+
* schema(node, options) {
|
|
25
|
+
* const { adapter, resolver, root } = this
|
|
26
|
+
* return <File ...><Type node={node} resolver={resolver} /></File>
|
|
27
|
+
* },
|
|
28
|
+
* operation(node, options) {
|
|
29
|
+
* return <File ...><OperationType node={node} /></File>
|
|
30
|
+
* },
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
74
33
|
*/
|
|
75
|
-
export type
|
|
34
|
+
export type Generator<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
|
|
35
|
+
/** Used in diagnostic messages and debug output. */
|
|
76
36
|
name: string
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Called for each schema node in the AST walk.
|
|
39
|
+
* `this` is the parent plugin's context with `adapter` and `rootNode` guaranteed present.
|
|
40
|
+
* `options` contains the per-node resolved options (after exclude/include/override).
|
|
41
|
+
*/
|
|
42
|
+
schema?: (
|
|
43
|
+
this: GeneratorContext<TOptions>,
|
|
44
|
+
node: SchemaNode,
|
|
45
|
+
options: TOptions['resolvedOptions'],
|
|
46
|
+
) => PossiblePromise<FabricReactNode | Array<FabricFile.File> | void>
|
|
47
|
+
/**
|
|
48
|
+
* Called for each operation node in the AST walk.
|
|
49
|
+
* `this` is the parent plugin's context with `adapter` and `rootNode` guaranteed present.
|
|
50
|
+
*/
|
|
51
|
+
operation?: (
|
|
52
|
+
this: GeneratorContext<TOptions>,
|
|
53
|
+
node: OperationNode,
|
|
54
|
+
options: TOptions['resolvedOptions'],
|
|
55
|
+
) => PossiblePromise<FabricReactNode | Array<FabricFile.File> | void>
|
|
56
|
+
/**
|
|
57
|
+
* Called once after all operations have been walked.
|
|
58
|
+
* `this` is the parent plugin's context with `adapter` and `rootNode` guaranteed present.
|
|
59
|
+
*/
|
|
60
|
+
operations?: (
|
|
61
|
+
this: GeneratorContext<TOptions>,
|
|
62
|
+
nodes: Array<OperationNode>,
|
|
63
|
+
options: TOptions['resolvedOptions'],
|
|
64
|
+
) => PossiblePromise<FabricReactNode | Array<FabricFile.File> | void>
|
|
82
65
|
}
|
|
83
66
|
|
|
84
67
|
/**
|
|
85
|
-
*
|
|
68
|
+
* Defines a generator. Returns the object as-is with correct `this` typings.
|
|
69
|
+
* No type discrimination (`type: 'react' | 'core'`) needed — `applyHookResult`
|
|
70
|
+
* handles React elements and `File[]` uniformly.
|
|
86
71
|
*/
|
|
87
|
-
export
|
|
88
|
-
|
|
89
|
-
type: 'react'
|
|
90
|
-
version: '2'
|
|
91
|
-
Operations(props: OperationsV2Props<TPlugin>): FabricReactNode
|
|
92
|
-
Operation(props: OperationV2Props<TPlugin>): FabricReactNode
|
|
93
|
-
Schema(props: SchemaV2Props<TPlugin>): FabricReactNode
|
|
72
|
+
export function defineGenerator<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(generator: Generator<TOptions>): Generator<TOptions> {
|
|
73
|
+
return generator
|
|
94
74
|
}
|
|
95
75
|
|
|
96
76
|
/**
|
|
97
|
-
*
|
|
98
|
-
*/
|
|
99
|
-
export type Generator<TPlugin extends PluginFactoryOptions = PluginFactoryOptions> = UserCoreGeneratorV2<TPlugin> | UserReactGeneratorV2<TPlugin>
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Defines a generator with no-op defaults for any omitted lifecycle methods.
|
|
103
|
-
* Works for both `core` (async file output) and `react` (JSX component) generators.
|
|
77
|
+
* Merges an array of generators into a single generator.
|
|
104
78
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* Schema({ node, options }) { return <File>...</File> },
|
|
112
|
-
* })
|
|
79
|
+
* The merged generator's `schema`, `operation`, and `operations` methods run
|
|
80
|
+
* the corresponding method from each input generator in sequence, applying each
|
|
81
|
+
* result via `applyHookResult`. This eliminates the need to write the loop
|
|
82
|
+
* manually in each plugin.
|
|
83
|
+
*
|
|
84
|
+
* @param generators - Array of generators to merge into a single generator.
|
|
113
85
|
*
|
|
114
86
|
* @example
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* const merged = mergeGenerators(generators)
|
|
89
|
+
*
|
|
90
|
+
* return {
|
|
91
|
+
* name: pluginName,
|
|
92
|
+
* schema: merged.schema,
|
|
93
|
+
* operation: merged.operation,
|
|
94
|
+
* operations: merged.operations,
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
121
97
|
*/
|
|
122
|
-
export function
|
|
123
|
-
generator: UserReactGeneratorV2<TPlugin>,
|
|
124
|
-
): ReactGeneratorV2<TPlugin>
|
|
125
|
-
|
|
126
|
-
export function defineGenerator<TPlugin extends PluginFactoryOptions = PluginFactoryOptions>(generator: UserCoreGeneratorV2<TPlugin>): CoreGeneratorV2<TPlugin>
|
|
127
|
-
export function defineGenerator<TPlugin extends PluginFactoryOptions = PluginFactoryOptions>(
|
|
128
|
-
generator: UserCoreGeneratorV2<TPlugin> | UserReactGeneratorV2<TPlugin>,
|
|
129
|
-
): unknown {
|
|
130
|
-
if (generator.type === 'react') {
|
|
131
|
-
return {
|
|
132
|
-
version: '2',
|
|
133
|
-
Operations() {
|
|
134
|
-
return null
|
|
135
|
-
},
|
|
136
|
-
Operation() {
|
|
137
|
-
return null
|
|
138
|
-
},
|
|
139
|
-
Schema() {
|
|
140
|
-
return null
|
|
141
|
-
},
|
|
142
|
-
...generator,
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
98
|
+
export function mergeGenerators<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(generators: Array<Generator<TOptions>>): Generator<TOptions> {
|
|
146
99
|
return {
|
|
147
|
-
|
|
148
|
-
async
|
|
149
|
-
|
|
100
|
+
name: generators.length > 0 ? generators.map((g) => g.name).join('+') : 'merged',
|
|
101
|
+
async schema(node, options) {
|
|
102
|
+
for (const gen of generators) {
|
|
103
|
+
if (!gen.schema) continue
|
|
104
|
+
const result = await gen.schema.call(this, node, options)
|
|
105
|
+
|
|
106
|
+
await applyHookResult(result, this.fabric)
|
|
107
|
+
}
|
|
150
108
|
},
|
|
151
|
-
async operation() {
|
|
152
|
-
|
|
109
|
+
async operation(node, options) {
|
|
110
|
+
for (const gen of generators) {
|
|
111
|
+
if (!gen.operation) continue
|
|
112
|
+
const result = await gen.operation.call(this, node, options)
|
|
113
|
+
|
|
114
|
+
await applyHookResult(result, this.fabric)
|
|
115
|
+
}
|
|
153
116
|
},
|
|
154
|
-
async
|
|
155
|
-
|
|
117
|
+
async operations(nodes, options) {
|
|
118
|
+
for (const gen of generators) {
|
|
119
|
+
if (!gen.operations) continue
|
|
120
|
+
const result = await gen.operations.call(this, nodes, options)
|
|
121
|
+
|
|
122
|
+
await applyHookResult(result, this.fabric)
|
|
123
|
+
}
|
|
156
124
|
},
|
|
157
|
-
...generator,
|
|
158
125
|
}
|
|
159
126
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { formatters, linters, logLevel } from './constants.ts'
|
|
|
6
6
|
export { createAdapter } from './createAdapter.ts'
|
|
7
7
|
export { createPlugin } from './createPlugin.ts'
|
|
8
8
|
export { createStorage } from './createStorage.ts'
|
|
9
|
-
export { defineGenerator } from './defineGenerator.ts'
|
|
9
|
+
export { defineGenerator, mergeGenerators } from './defineGenerator.ts'
|
|
10
10
|
export { defineLogger } from './defineLogger.ts'
|
|
11
11
|
export { definePresets } from './definePresets.ts'
|
|
12
12
|
export {
|
|
@@ -19,7 +19,6 @@ export {
|
|
|
19
19
|
defineResolver,
|
|
20
20
|
} from './defineResolver.ts'
|
|
21
21
|
export { getMode, PluginDriver } from './PluginDriver.ts'
|
|
22
|
-
export { renderOperation, renderOperations, renderSchema, runGeneratorOperation, runGeneratorOperations, runGeneratorSchema } from './renderNode.tsx'
|
|
23
22
|
export { fsStorage } from './storages/fsStorage.ts'
|
|
24
23
|
export { memoryStorage } from './storages/memoryStorage.ts'
|
|
25
24
|
export * from './types.ts'
|