@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "5.0.0-alpha.29",
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.29"
74
+ "@kubb/ast": "5.0.0-alpha.30"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@types/semver": "^7.7.1",
@@ -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({ install() {} }, plugin) as unknown as Plugin)
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
- events: driver.options.events,
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 plugin of this.plugins.values()) {
171
- if (typeof plugin.inject === 'function') {
172
- const result = (plugin.inject as (this: PluginContext, context: PluginContext) => unknown).call(
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<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(pluginName: string): Plugin<TOptions> | undefined {
486
- return this.plugins.get(pluginName) as Plugin<TOptions> | undefined
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, Output, Plugin, Storage, UserConfig } from './types.ts'
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 installer = plugin.install.bind(context)
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: ['Installing plugin...', ` • Plugin Name: ${plugin.name}`],
345
+ logs: ['Starting plugin...', ` • Plugin Name: ${plugin.name}`],
291
346
  })
292
347
 
293
- await installer(context)
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 installed successfully (${formatMs(duration)})`],
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 installation failed',
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 as { output?: Output<unknown> } | undefined
499
+ const pluginOptions = plugin?.options
421
500
 
422
501
  if (!pluginOptions || pluginOptions.output?.barrelType === false) {
423
502
  return []
@@ -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>,
@@ -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 type { PluginDriver } from './PluginDriver.ts'
5
- import type { Adapter, Config, Plugin, PluginFactoryOptions } from './types.ts'
5
+ import { applyHookResult } from './renderNode.tsx'
6
+ import type { GeneratorContext, PluginFactoryOptions } from './types.ts'
6
7
 
7
- export type Version = '1' | '2'
8
+ export type { GeneratorContext } from './types.ts'
8
9
 
9
10
  /**
10
- * Props for the `operations` lifecycle receives all operation nodes at once.
11
- */
12
- export type OperationsV2Props<TPlugin extends PluginFactoryOptions = PluginFactoryOptions> = {
13
- config: Config
14
- plugin: Plugin<TPlugin>
15
- adapter: Adapter
16
- driver: PluginDriver
17
- options: Plugin<TPlugin>['options']
18
- resolver: TPlugin['resolver']
19
- nodes: Array<OperationNode>
20
- }
21
-
22
- /**
23
- * Props for the `operation` lifecycle — receives a single operation node.
24
- */
25
- export type OperationV2Props<TPlugin extends PluginFactoryOptions = PluginFactoryOptions> = {
26
- config: Config
27
- adapter: Adapter
28
- driver: PluginDriver
29
- plugin: Plugin<TPlugin>
30
- options: Plugin<TPlugin>['options']
31
- resolver: TPlugin['resolver']
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 CoreGeneratorV2<TPlugin extends PluginFactoryOptions = PluginFactoryOptions> = {
34
+ export type Generator<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
35
+ /** Used in diagnostic messages and debug output. */
76
36
  name: string
77
- type: 'core'
78
- version: '2'
79
- operations(props: OperationsV2Props<TPlugin>): Promise<Array<FabricFile.File>>
80
- operation(props: OperationV2Props<TPlugin>): Promise<Array<FabricFile.File>>
81
- schema(props: SchemaV2Props<TPlugin>): Promise<Array<FabricFile.File>>
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
- * A fully resolved React v2 generator with `version: '2'` and guaranteed component methods.
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 type ReactGeneratorV2<TPlugin extends PluginFactoryOptions = PluginFactoryOptions> = {
88
- name: string
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
- * Union of all v2 generator shapes accepted by the plugin system.
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
- * @example
106
- * // react generator
107
- * export const typeGenerator = defineGenerator<PluginTs>({
108
- * name: 'typescript',
109
- * type: 'react',
110
- * Operation({ node, options }) { return <File>...</File> },
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
- * // core generator
116
- * export const myGenerator = defineGenerator<MyPlugin>({
117
- * name: 'my-generator',
118
- * type: 'core',
119
- * async operation({ node, options }) { return [{ path: '...', content: '...' }] },
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 defineGenerator<TPlugin extends PluginFactoryOptions = PluginFactoryOptions>(
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
- version: '2',
148
- async operations() {
149
- return []
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
- return []
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 schema() {
155
- return []
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'