@kubb/core 5.0.0-alpha.8 → 5.0.0-beta.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +23 -20
  2. package/dist/PluginDriver-BXibeQk-.cjs +1036 -0
  3. package/dist/PluginDriver-BXibeQk-.cjs.map +1 -0
  4. package/dist/PluginDriver-DV3p2Hky.js +945 -0
  5. package/dist/PluginDriver-DV3p2Hky.js.map +1 -0
  6. package/dist/index.cjs +756 -1693
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +297 -239
  9. package/dist/index.js +743 -1661
  10. package/dist/index.js.map +1 -1
  11. package/dist/mocks.cjs +145 -0
  12. package/dist/mocks.cjs.map +1 -0
  13. package/dist/mocks.d.ts +80 -0
  14. package/dist/mocks.js +140 -0
  15. package/dist/mocks.js.map +1 -0
  16. package/dist/types-CuNocrbJ.d.ts +2148 -0
  17. package/package.json +51 -57
  18. package/src/FileManager.ts +115 -0
  19. package/src/FileProcessor.ts +86 -0
  20. package/src/Kubb.ts +208 -160
  21. package/src/PluginDriver.ts +326 -565
  22. package/src/constants.ts +20 -47
  23. package/src/createAdapter.ts +16 -6
  24. package/src/createKubb.ts +548 -0
  25. package/src/createRenderer.ts +57 -0
  26. package/src/createStorage.ts +40 -26
  27. package/src/defineGenerator.ts +87 -0
  28. package/src/defineLogger.ts +19 -0
  29. package/src/defineMiddleware.ts +62 -0
  30. package/src/defineParser.ts +44 -0
  31. package/src/definePlugin.ts +83 -0
  32. package/src/defineResolver.ts +521 -0
  33. package/src/devtools.ts +14 -14
  34. package/src/index.ts +14 -17
  35. package/src/mocks.ts +178 -0
  36. package/src/renderNode.ts +35 -0
  37. package/src/storages/fsStorage.ts +41 -11
  38. package/src/storages/memoryStorage.ts +4 -2
  39. package/src/types.ts +1054 -270
  40. package/src/utils/diagnostics.ts +4 -1
  41. package/src/utils/isInputPath.ts +10 -0
  42. package/src/utils/packageJSON.ts +99 -0
  43. package/dist/PluginDriver-DRfJIbG1.d.ts +0 -1056
  44. package/dist/chunk-ByKO4r7w.cjs +0 -38
  45. package/dist/hooks.cjs +0 -102
  46. package/dist/hooks.cjs.map +0 -1
  47. package/dist/hooks.d.ts +0 -75
  48. package/dist/hooks.js +0 -97
  49. package/dist/hooks.js.map +0 -1
  50. package/src/PackageManager.ts +0 -180
  51. package/src/build.ts +0 -419
  52. package/src/config.ts +0 -56
  53. package/src/createGenerator.ts +0 -106
  54. package/src/createLogger.ts +0 -7
  55. package/src/createPlugin.ts +0 -12
  56. package/src/errors.ts +0 -1
  57. package/src/hooks/index.ts +0 -4
  58. package/src/hooks/useKubb.ts +0 -138
  59. package/src/hooks/useMode.ts +0 -11
  60. package/src/hooks/usePlugin.ts +0 -11
  61. package/src/hooks/usePluginDriver.ts +0 -11
  62. package/src/utils/FunctionParams.ts +0 -155
  63. package/src/utils/TreeNode.ts +0 -215
  64. package/src/utils/executeStrategies.ts +0 -81
  65. package/src/utils/formatters.ts +0 -56
  66. package/src/utils/getBarrelFiles.ts +0 -141
  67. package/src/utils/getConfigs.ts +0 -30
  68. package/src/utils/getPlugins.ts +0 -23
  69. package/src/utils/linters.ts +0 -25
  70. package/src/utils/resolveOptions.ts +0 -93
package/src/mocks.ts ADDED
@@ -0,0 +1,178 @@
1
+ import { resolve } from 'node:path'
2
+ import type { FileNode, OperationNode, SchemaNode, Visitor } from '@kubb/ast'
3
+ import { transform } from '@kubb/ast'
4
+ import { FileManager } from './FileManager.ts'
5
+ import { PluginDriver } from './PluginDriver.ts'
6
+ import { applyHookResult } from './renderNode.ts'
7
+ import type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions } from './types.ts'
8
+
9
+ /**
10
+
11
+ * Creates a minimal `PluginDriver` mock for unit tests.
12
+ */
13
+ export function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): PluginDriver {
14
+ return {
15
+ config: options?.config ?? {
16
+ root: '.',
17
+ output: {
18
+ path: './path',
19
+ },
20
+ },
21
+ getPlugin(_pluginName: string): NormalizedPlugin | undefined {
22
+ return options?.plugin
23
+ },
24
+ getResolver: (_pluginName: string) => options?.plugin?.resolver,
25
+ fileManager: new FileManager(),
26
+ } as unknown as PluginDriver
27
+ }
28
+
29
+ /**
30
+ * Creates a minimal `Adapter` mock for unit tests.
31
+ * `parse` returns an empty `InputNode` by default; override via `options.parse`.
32
+ * `getImports` returns `[]` by default.
33
+ */
34
+ export function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(
35
+ options: {
36
+ name?: TOptions['name']
37
+ resolvedOptions?: TOptions['resolvedOptions']
38
+ inputNode?: Adapter<TOptions>['inputNode']
39
+ parse?: Adapter<TOptions>['parse']
40
+ getImports?: Adapter<TOptions>['getImports']
41
+ } = {},
42
+ ): Adapter<TOptions> {
43
+ const inputNode = options.inputNode ?? null
44
+ return {
45
+ name: (options.name ?? 'oas') as TOptions['name'],
46
+ options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],
47
+ inputNode,
48
+ parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),
49
+ getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),
50
+ } as Adapter<TOptions>
51
+ }
52
+
53
+ /**
54
+ * Creates a minimal plugin mock for unit tests.
55
+ *
56
+ * @example
57
+ * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`
58
+ */
59
+ export function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {
60
+ name: TOptions['name']
61
+ options: TOptions['resolvedOptions']
62
+ resolver?: TOptions['resolver']
63
+ transformer?: Visitor
64
+ dependencies?: Array<string>
65
+ }): NormalizedPlugin<TOptions> {
66
+ return {
67
+ name: params.name,
68
+ options: params.options,
69
+ resolver: params.resolver,
70
+ transformer: params.transformer,
71
+ dependencies: params.dependencies,
72
+ hooks: {},
73
+ } as unknown as NormalizedPlugin<TOptions>
74
+ }
75
+
76
+ type RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {
77
+ config: Config
78
+ adapter: Adapter
79
+ driver: PluginDriver
80
+ plugin: NormalizedPlugin<TOptions>
81
+ options: TOptions['resolvedOptions']
82
+ resolver: TOptions['resolver']
83
+ }
84
+
85
+ function createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {
86
+ const root = resolve(opts.config.root, opts.config.output.path)
87
+
88
+ return {
89
+ config: opts.config,
90
+ root,
91
+ getMode: (output: { path: string }) => PluginDriver.getMode(resolve(root, output.path)),
92
+ adapter: opts.adapter,
93
+ resolver: opts.resolver,
94
+ plugin: opts.plugin,
95
+ driver: opts.driver,
96
+ getResolver: (name: string) => opts.driver.getResolver(name),
97
+ inputNode: { kind: 'Input', schemas: [], operations: [] },
98
+ addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),
99
+ upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),
100
+ hooks: opts.driver.hooks ?? ({} as never),
101
+ warn: (msg: string) => console.warn(msg),
102
+ error: (msg: string) => console.error(msg),
103
+ info: (msg: string) => console.info(msg),
104
+ openInStudio: async () => {},
105
+ } as unknown as Omit<GeneratorContext<TOptions>, 'options'>
106
+ }
107
+
108
+ /**
109
+ * Renders a generator's `schema` method in a test context.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })
114
+ * await matchFiles(driver.fileManager.files)
115
+ * ```
116
+ */
117
+ export async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(
118
+ generator: Generator<TOptions>,
119
+ node: SchemaNode,
120
+ opts: RenderGeneratorOptions<TOptions>,
121
+ ): Promise<void> {
122
+ if (!generator.schema) return
123
+ const context = createMockedPluginContext(opts)
124
+ const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node
125
+ const result = await generator.schema(transformedNode, {
126
+ ...context,
127
+ options: opts.options,
128
+ })
129
+ await applyHookResult(result, opts.driver, generator.renderer ?? undefined)
130
+ }
131
+
132
+ /**
133
+ * Renders a generator's `operation` method in a test context.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })
138
+ * await matchFiles(driver.fileManager.files)
139
+ * ```
140
+ */
141
+ export async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(
142
+ generator: Generator<TOptions>,
143
+ node: OperationNode,
144
+ opts: RenderGeneratorOptions<TOptions>,
145
+ ): Promise<void> {
146
+ if (!generator.operation) return
147
+ const context = createMockedPluginContext(opts)
148
+ const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node
149
+ const result = await generator.operation(transformedNode, {
150
+ ...context,
151
+ options: opts.options,
152
+ })
153
+ await applyHookResult(result, opts.driver, generator.renderer ?? undefined)
154
+ }
155
+
156
+ /**
157
+ * Renders a generator's `operations` method in a test context.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })
162
+ * await matchFiles(driver.fileManager.files)
163
+ * ```
164
+ */
165
+ export async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(
166
+ generator: Generator<TOptions>,
167
+ nodes: Array<OperationNode>,
168
+ opts: RenderGeneratorOptions<TOptions>,
169
+ ): Promise<void> {
170
+ if (!generator.operations) return
171
+ const context = createMockedPluginContext(opts)
172
+ const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes
173
+ const result = await generator.operations(transformedNodes, {
174
+ ...context,
175
+ options: opts.options,
176
+ })
177
+ await applyHookResult(result, opts.driver, generator.renderer ?? undefined)
178
+ }
@@ -0,0 +1,35 @@
1
+ import type { FileNode } from '@kubb/ast'
2
+ import type { RendererFactory } from './createRenderer.ts'
3
+ import type { PluginDriver } from './PluginDriver.ts'
4
+
5
+ /**
6
+ * Handles the return value of a plugin AST hook or generator method.
7
+ *
8
+ * - Renderer output → rendered via the provided `rendererFactory` (e.g. JSX), files stored in `driver.fileManager`
9
+ * - `Array<FileNode>` → added directly into `driver.fileManager`
10
+ * - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
11
+ *
12
+ * Pass a `rendererFactory` (e.g. `jsxRenderer` from `@kubb/renderer-jsx`) when the result
13
+ * may be a renderer element. Generators that only return `Array<FileNode>` do not need one.
14
+ */
15
+ export async function applyHookResult<TElement = unknown>(
16
+ result: TElement | Array<FileNode> | void,
17
+ driver: PluginDriver,
18
+ rendererFactory?: RendererFactory<TElement>,
19
+ ): Promise<void> {
20
+ if (!result) return
21
+
22
+ if (Array.isArray(result)) {
23
+ driver.fileManager.upsert(...(result as Array<FileNode>))
24
+ return
25
+ }
26
+
27
+ if (!rendererFactory) {
28
+ return
29
+ }
30
+
31
+ const renderer = rendererFactory()
32
+ await renderer.render(result)
33
+ driver.fileManager.upsert(...renderer.files)
34
+ renderer.unmount()
35
+ }
@@ -4,10 +4,17 @@ import { join, resolve } from 'node:path'
4
4
  import { clean, write } from '@internals/utils'
5
5
  import { createStorage } from '../createStorage.ts'
6
6
 
7
+ /**
8
+ * Detects the filesystem error used to indicate that a path does not exist.
9
+ */
10
+ function isMissingPathError(error: unknown): error is NodeJS.ErrnoException {
11
+ return typeof error === 'object' && error !== null && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT'
12
+ }
13
+
7
14
  /**
8
15
  * Built-in filesystem storage driver.
9
16
  *
10
- * This is the default storage when no `storage` option is configured in `output`.
17
+ * This is the default storage when no `storage` option is configured in the root config.
11
18
  * Keys are resolved against `process.cwd()`, so root-relative paths such as
12
19
  * `src/gen/api/getPets.ts` are written to the correct location without extra configuration.
13
20
  *
@@ -19,11 +26,13 @@ import { createStorage } from '../createStorage.ts'
19
26
  *
20
27
  * @example
21
28
  * ```ts
22
- * import { defineConfig, fsStorage } from '@kubb/core'
29
+ * import { fsStorage } from '@kubb/core'
30
+ * import { defineConfig } from 'kubb'
23
31
  *
24
32
  * export default defineConfig({
25
33
  * input: { path: './petStore.yaml' },
26
- * output: { path: './src/gen', storage: fsStorage() },
34
+ * output: { path: './src/gen' },
35
+ * storage: fsStorage(),
27
36
  * })
28
37
  * ```
29
38
  */
@@ -33,15 +42,27 @@ export const fsStorage = createStorage(() => ({
33
42
  try {
34
43
  await access(resolve(key))
35
44
  return true
36
- } catch {
37
- return false
45
+ } catch (error) {
46
+ if (isMissingPathError(error)) {
47
+ return false
48
+ }
49
+
50
+ throw new Error(`Failed to access storage item "${key}"`, {
51
+ cause: error as Error,
52
+ })
38
53
  }
39
54
  },
40
55
  async getItem(key: string) {
41
56
  try {
42
57
  return await readFile(resolve(key), 'utf8')
43
- } catch {
44
- return null
58
+ } catch (error) {
59
+ if (isMissingPathError(error)) {
60
+ return null
61
+ }
62
+
63
+ throw new Error(`Failed to read storage item "${key}"`, {
64
+ cause: error as Error,
65
+ })
45
66
  }
46
67
  },
47
68
  async setItem(key: string, value: string) {
@@ -52,13 +73,22 @@ export const fsStorage = createStorage(() => ({
52
73
  },
53
74
  async getKeys(base?: string) {
54
75
  const keys: Array<string> = []
76
+ const resolvedBase = resolve(base ?? process.cwd())
55
77
 
56
78
  async function walk(dir: string, prefix: string): Promise<void> {
57
79
  let entries: Array<Dirent>
58
80
  try {
59
- entries = (await readdir(dir, { withFileTypes: true })) as Array<Dirent>
60
- } catch {
61
- return
81
+ entries = (await readdir(dir, {
82
+ withFileTypes: true,
83
+ })) as Array<Dirent>
84
+ } catch (error) {
85
+ if (isMissingPathError(error)) {
86
+ return
87
+ }
88
+
89
+ throw new Error(`Failed to list storage keys under "${resolvedBase}"`, {
90
+ cause: error as Error,
91
+ })
62
92
  }
63
93
  for (const entry of entries) {
64
94
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name
@@ -70,7 +100,7 @@ export const fsStorage = createStorage(() => ({
70
100
  }
71
101
  }
72
102
 
73
- await walk(resolve(base ?? process.cwd()), '')
103
+ await walk(resolvedBase, '')
74
104
 
75
105
  return keys
76
106
  },
@@ -9,11 +9,13 @@ import { createStorage } from '../createStorage.ts'
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * import { defineConfig, memoryStorage } from '@kubb/core'
12
+ * import { memoryStorage } from '@kubb/core'
13
+ * import { defineConfig } from 'kubb'
13
14
  *
14
15
  * export default defineConfig({
15
16
  * input: { path: './petStore.yaml' },
16
- * output: { path: './src/gen', storage: memoryStorage() },
17
+ * output: { path: './src/gen' },
18
+ * storage: memoryStorage(),
17
19
  * })
18
20
  * ```
19
21
  */