@kubb/core 5.0.0-beta.20 → 5.0.0-beta.21

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/mocks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { FileManager } from './FileManager.ts'\nimport { applyHookResult, KubbDriver } from './KubbDriver.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions } from './types.ts'\n\n/**\n\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager: new FileManager(),\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default; override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n getMode: (output: { path: string }) => KubbDriver.getMode(resolve(root, output.path)),\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n openInStudio: async () => {},\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer ?? undefined })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer ?? undefined })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer ?? undefined })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { FileManager } from './FileManager.ts'\nimport { applyHookResult, KubbDriver } from './KubbDriver.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions } from './types.ts'\n\n/**\n\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager: new FileManager(),\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default; override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n getMode: (output: { path: string }) => KubbDriver.getMode(resolve(root, output.path)),\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n openInStudio: async () => {},\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await applyHookResult({ result, driver: opts.driver, rendererFactory: generator.renderer })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/core",
3
- "version": "5.0.0-beta.20",
3
+ "version": "5.0.0-beta.21",
4
4
  "description": "Core engine for Kubb's plugin-based code generation system. Provides the plugin driver, file manager, defineConfig, and build orchestration used by every Kubb plugin.",
5
5
  "keywords": [
6
6
  "code-generator",
@@ -58,14 +58,14 @@
58
58
  "dependencies": {
59
59
  "fflate": "^0.8.3",
60
60
  "tinyexec": "^1.1.2",
61
- "@kubb/ast": "5.0.0-beta.20"
61
+ "@kubb/ast": "5.0.0-beta.21"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@internals/utils": "0.0.0",
65
- "@kubb/renderer-jsx": "5.0.0-beta.20"
65
+ "@kubb/renderer-jsx": "5.0.0-beta.21"
66
66
  },
67
67
  "peerDependencies": {
68
- "@kubb/renderer-jsx": "5.0.0-beta.20"
68
+ "@kubb/renderer-jsx": "5.0.0-beta.21"
69
69
  },
70
70
  "size-limit": [
71
71
  {
@@ -9,72 +9,91 @@ function mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNod
9
9
  // at the same path.
10
10
  banner: b.banner,
11
11
  footer: b.footer,
12
- sources: [...(a.sources || []), ...(b.sources || [])],
13
- imports: [...(a.imports || []), ...(b.imports || [])],
14
- exports: [...(a.exports || []), ...(b.exports || [])],
12
+ sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,
13
+ imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,
14
+ exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,
15
15
  }
16
16
  }
17
17
 
18
- /**
19
- * Collapses a list of files so that duplicates sharing the same `path` are merged
20
- * in arrival order. Keeps the original order of first occurrence.
21
- */
22
- function mergeFilesByPath(files: ReadonlyArray<FileNode>): Map<string, FileNode> {
23
- const merged = new Map<string, FileNode>()
24
- for (const file of files) {
25
- const existing = merged.get(file.path)
26
- merged.set(file.path, existing ? mergeFile(existing, file) : file)
27
- }
28
- return merged
18
+ function isIndexPath(path: string): boolean {
19
+ return path.endsWith('/index.ts') || path === 'index.ts'
20
+ }
21
+
22
+ // Sort order: shortest path first; within a length bucket, index.ts barrels last.
23
+ function compareFiles(a: FileNode, b: FileNode): number {
24
+ const lenDiff = a.path.length - b.path.length
25
+ if (lenDiff !== 0) return lenDiff
26
+ const aIsIndex = isIndexPath(a.path)
27
+ const bIsIndex = isIndexPath(b.path)
28
+ if (aIsIndex && !bIsIndex) return 1
29
+ if (!aIsIndex && bIsIndex) return -1
30
+ return 0
29
31
  }
30
32
 
31
33
  /**
32
- * In-memory file store for generated files.
33
- *
34
- * Files with the same `path` are merged sources, imports, and exports are concatenated.
35
- * The `files` getter returns all stored files sorted by path length (shortest first).
34
+ * In-memory file store for generated files. Files sharing a `path` are merged
35
+ * (sources/imports/exports concatenated). The `files` getter is sorted by
36
+ * path length (barrel `index.ts` last within a bucket).
36
37
  *
37
38
  * @example
38
39
  * ```ts
39
- * import { FileManager } from '@kubb/core'
40
- *
41
40
  * const manager = new FileManager()
42
41
  * manager.upsert(myFile)
43
- * console.log(manager.files) // all stored files
42
+ * manager.files // sorted view
44
43
  * ```
45
44
  */
46
45
  export class FileManager {
47
46
  readonly #cache = new Map<string, FileNode>()
48
- #filesCache: Array<FileNode> | null = null
47
+ // Cached sorted view; null means stale and rebuilt lazily on next `files` read.
48
+ // Nulled (not mutated) on every write so callers holding a prior reference
49
+ // keep their snapshot — `dispose()` must not silently empty an array the
50
+ // consumer already holds.
51
+ #sorted: Array<FileNode> | null = null
52
+ #onUpsert: ((file: FileNode) => void) | null = null
49
53
 
50
54
  /**
51
- * Adds one or more files. Incoming files with the same path are merged
52
- * (sources/imports/exports concatenated), but existing cache entries are
53
- * replaced use {@link upsert} when you want to merge into the cache too.
55
+ * Registers a callback invoked with the resolved {@link FileNode} on every
56
+ * `add` / `upsert`. Used by the build loop to track newly written files
57
+ * without keeping its own scan-based diff. Single subscriber by design
58
+ * setting again replaces the previous callback. Pass `null` to detach.
54
59
  */
60
+ setOnUpsert(callback: ((file: FileNode) => void) | null): void {
61
+ this.#onUpsert = callback
62
+ }
63
+
55
64
  add(...files: Array<FileNode>): Array<FileNode> {
56
65
  return this.#store(files, false)
57
66
  }
58
67
 
59
- /**
60
- * Adds or merges one or more files.
61
- * If a file with the same path already exists in the cache, its
62
- * sources/imports/exports are merged into the incoming file.
63
- */
64
68
  upsert(...files: Array<FileNode>): Array<FileNode> {
65
69
  return this.#store(files, true)
66
70
  }
67
71
 
68
72
  #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {
69
- const resolvedFiles: Array<FileNode> = []
70
- for (const file of mergeFilesByPath(files).values()) {
71
- const existing = mergeExisting ? this.#cache.get(file.path) : undefined
72
- const resolvedFile = createFile(existing ? mergeFile(existing, file) : file)
73
- this.#cache.set(resolvedFile.path, resolvedFile)
74
- resolvedFiles.push(resolvedFile)
73
+ const batch = files.length > 1 ? this.#dedupe(files) : files
74
+ const resolved: Array<FileNode> = []
75
+
76
+ for (const file of batch) {
77
+ const existing = this.#cache.get(file.path)
78
+ const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : file
79
+ this.#cache.set(merged.path, merged)
80
+ resolved.push(merged)
81
+ this.#onUpsert?.(merged)
75
82
  }
76
- this.#filesCache = null
77
- return resolvedFiles
83
+
84
+ if (resolved.length > 0) this.#sorted = null
85
+ return resolved
86
+ }
87
+
88
+ // Merges same-path entries within a batch so the cache update loop stays
89
+ // uniform. Only called for multi-file batches.
90
+ #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {
91
+ const seen = new Map<string, FileNode>()
92
+ for (const file of files) {
93
+ const prev = seen.get(file.path)
94
+ seen.set(file.path, prev ? mergeFile(prev, file) : file)
95
+ }
96
+ return [...seen.values()]
78
97
  }
79
98
 
80
99
  getByPath(path: string): FileNode | null {
@@ -82,21 +101,21 @@ export class FileManager {
82
101
  }
83
102
 
84
103
  deleteByPath(path: string): void {
85
- this.#cache.delete(path)
86
- this.#filesCache = null
104
+ if (!this.#cache.delete(path)) return
105
+ this.#sorted = null
87
106
  }
88
107
 
89
108
  clear(): void {
90
109
  this.#cache.clear()
91
- this.#filesCache = null
110
+ this.#sorted = null
92
111
  }
93
112
 
94
113
  /**
95
- * Releases all stored files. Called by the core after `kubb:build:end` to
96
- * free the per-plugin FileNode caches for the rest of the process lifetime.
114
+ * Releases all stored files. Called by the core after `kubb:build:end`.
97
115
  */
98
116
  dispose(): void {
99
117
  this.clear()
118
+ this.#onUpsert = null
100
119
  }
101
120
 
102
121
  [Symbol.dispose](): void {
@@ -104,24 +123,10 @@ export class FileManager {
104
123
  }
105
124
 
106
125
  /**
107
- * All stored files, sorted by path length (shorter paths first).
126
+ * All stored files in stable sort order (shortest path first, barrel files
127
+ * last within a length bucket). Returns a cached view — do not mutate.
108
128
  */
109
129
  get files(): Array<FileNode> {
110
- if (this.#filesCache) {
111
- return this.#filesCache
112
- }
113
-
114
- this.#filesCache = [...this.#cache.values()].sort((a, b) => {
115
- const lenDiff = a.path.length - b.path.length
116
- if (lenDiff !== 0) return lenDiff
117
- // Within the same length bucket, index.ts barrel files go last so other
118
- // files are always processed before their barrel file.
119
- const aIsIndex = a.path.endsWith('/index.ts') || a.path === 'index.ts'
120
- const bIsIndex = b.path.endsWith('/index.ts') || b.path === 'index.ts'
121
- if (aIsIndex && !bIsIndex) return 1
122
- if (!aIsIndex && bIsIndex) return -1
123
- return 0
124
- })
125
- return this.#filesCache
130
+ return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))
126
131
  }
127
132
  }
@@ -82,4 +82,15 @@ export class FileProcessor {
82
82
 
83
83
  return files
84
84
  }
85
+
86
+ /**
87
+ * Clears all registered event listeners.
88
+ */
89
+ dispose(): void {
90
+ this.events.removeAll()
91
+ }
92
+
93
+ [Symbol.dispose](): void {
94
+ this.dispose()
95
+ }
85
96
  }