@kubb/core 5.0.0-alpha.20 → 5.0.0-alpha.22

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/index.cjs CHANGED
@@ -971,6 +971,15 @@ function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
971
971
  }
972
972
  //#endregion
973
973
  //#region src/PluginDriver.ts
974
+ /**
975
+ * Returns `'single'` when `fileOrFolder` has a file extension, `'split'` otherwise.
976
+ *
977
+ * @example
978
+ * ```ts
979
+ * getMode('src/gen/types.ts') // 'single'
980
+ * getMode('src/gen/types') // 'split'
981
+ * ```
982
+ */
974
983
  function getMode(fileOrFolder) {
975
984
  if (!fileOrFolder) return "split";
976
985
  return (0, node_path.extname)(fileOrFolder) ? "single" : "split";
@@ -1469,7 +1478,7 @@ const fsStorage = createStorage(() => ({
1469
1478
  }));
1470
1479
  //#endregion
1471
1480
  //#region package.json
1472
- var version = "5.0.0-alpha.20";
1481
+ var version = "5.0.0-alpha.22";
1473
1482
  //#endregion
1474
1483
  //#region src/utils/diagnostics.ts
1475
1484
  /**
@@ -1857,6 +1866,26 @@ function createPlugin(build) {
1857
1866
  return (options) => build(options ?? {});
1858
1867
  }
1859
1868
  //#endregion
1869
+ //#region src/defineBuilder.ts
1870
+ /**
1871
+ * Defines a builder for a plugin — a named collection of schema-building helpers that
1872
+ * can be exported alongside the plugin and imported by other plugins or generators.
1873
+ *
1874
+ * @example
1875
+ * export const builder = defineBuilder<PluginTs>(() => ({
1876
+ * name: 'default',
1877
+ * buildParamsSchema({ params, node, resolver }) {
1878
+ * return createSchema({ type: 'object', properties: [] })
1879
+ * },
1880
+ * buildDataSchemaNode({ node, resolver }) {
1881
+ * return createSchema({ type: 'object', properties: [] })
1882
+ * },
1883
+ * }))
1884
+ */
1885
+ function defineBuilder(build) {
1886
+ return build();
1887
+ }
1888
+ //#endregion
1860
1889
  //#region src/defineGenerator.ts
1861
1890
  function defineGenerator(generator) {
1862
1891
  if (generator.type === "react") return {
@@ -1906,8 +1935,8 @@ function defineLogger(logger) {
1906
1935
  //#endregion
1907
1936
  //#region src/definePreset.ts
1908
1937
  /**
1909
- * Creates a typed preset object that bundles a name, resolvers, and optional
1910
- * transformers — the building block for composable plugin presets.
1938
+ * Creates a typed preset object that bundles a name, resolvers, optional
1939
+ * transformers, and optional generators — the building block for composable plugin presets.
1911
1940
  *
1912
1941
  * @example
1913
1942
  * import { definePreset } from '@kubb/core'
@@ -1918,12 +1947,17 @@ function defineLogger(logger) {
1918
1947
  * @example
1919
1948
  * // With custom transformers
1920
1949
  * export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy], transformers: [myTransformer] })
1950
+ *
1951
+ * @example
1952
+ * // With generators
1953
+ * export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy], generators: [typeGeneratorLegacy] })
1921
1954
  */
1922
- function definePreset(name, { resolvers, transformers }) {
1955
+ function definePreset(name, { resolvers, transformers, generators }) {
1923
1956
  return {
1924
1957
  name,
1925
1958
  resolvers,
1926
- transformers
1959
+ transformers,
1960
+ generators
1927
1961
  };
1928
1962
  }
1929
1963
  //#endregion
@@ -1958,6 +1992,7 @@ function matchesOperationPattern(node, type, pattern) {
1958
1992
  }
1959
1993
  /**
1960
1994
  * Checks if a schema matches a pattern for a given filter type (`schemaName`).
1995
+ *
1961
1996
  * Returns `null` when the filter type doesn't apply to schemas.
1962
1997
  */
1963
1998
  function matchesSchemaPattern(node, type, pattern) {
@@ -1967,7 +2002,11 @@ function matchesSchemaPattern(node, type, pattern) {
1967
2002
  }
1968
2003
  }
1969
2004
  /**
1970
- * Default name resolver `camelCase` for most types, `PascalCase` for `type`.
2005
+ * Default name resolver used by `defineResolver`.
2006
+ *
2007
+ * - `camelCase` for `function` and `file` types.
2008
+ * - `PascalCase` for `type`.
2009
+ * - `camelCase` for everything else.
1971
2010
  */
1972
2011
  function defaultResolver(name, type) {
1973
2012
  let resolvedName = camelCase(name);
@@ -1976,8 +2015,27 @@ function defaultResolver(name, type) {
1976
2015
  return resolvedName;
1977
2016
  }
1978
2017
  /**
1979
- * Default option resolver — applies include/exclude filters and merges any matching override options.
1980
- * Returns `null` when the node is filtered out.
2018
+ * Default option resolver — applies include/exclude filters and merges matching override options.
2019
+ *
2020
+ * Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
2021
+ *
2022
+ * @example Include/exclude filtering
2023
+ * ```ts
2024
+ * const options = defaultResolveOptions(operationNode, {
2025
+ * options: { output: 'types' },
2026
+ * exclude: [{ type: 'tag', pattern: 'internal' }],
2027
+ * })
2028
+ * // → null when node has tag 'internal'
2029
+ * ```
2030
+ *
2031
+ * @example Override merging
2032
+ * ```ts
2033
+ * const options = defaultResolveOptions(operationNode, {
2034
+ * options: { enumType: 'asConst' },
2035
+ * override: [{ type: 'operationId', pattern: 'listPets', options: { enumType: 'enum' } }],
2036
+ * })
2037
+ * // → { enumType: 'enum' } when operationId matches
2038
+ * ```
1981
2039
  */
1982
2040
  function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
1983
2041
  if ((0, _kubb_ast.isOperationNode)(node)) {
@@ -2004,27 +2062,245 @@ function defaultResolveOptions(node, { options, exclude = [], include, override
2004
2062
  return options;
2005
2063
  }
2006
2064
  /**
2007
- * Defines a resolver for a plugin, with built-in defaults for name casing and include/exclude/override filtering.
2008
- * Override `default` or `resolveOptions` in the builder to customize the behavior.
2065
+ * Default path resolver used by `defineResolver`.
2009
2066
  *
2010
- * @example
2067
+ * - Returns the output directory in `single` mode.
2068
+ * - Resolves into a tag- or path-based subdirectory when `group` and a `tag`/`path` value are provided.
2069
+ * - Falls back to a flat `output/baseName` path otherwise.
2070
+ *
2071
+ * A custom `group.name` function overrides the default subdirectory naming.
2072
+ * For `tag` groups the default is `${camelCase(tag)}Controller`.
2073
+ * For `path` groups the default is the first path segment after `/`.
2074
+ *
2075
+ * @example Flat output
2076
+ * ```ts
2077
+ * defaultResolvePath({ baseName: 'petTypes.ts' }, { root: '/src', output: { path: 'types' } })
2078
+ * // → '/src/types/petTypes.ts'
2079
+ * ```
2080
+ *
2081
+ * @example Tag-based grouping
2082
+ * ```ts
2083
+ * defaultResolvePath(
2084
+ * { baseName: 'petTypes.ts', tag: 'pets' },
2085
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
2086
+ * )
2087
+ * // → '/src/types/petsController/petTypes.ts'
2088
+ * ```
2089
+ *
2090
+ * @example Path-based grouping
2091
+ * ```ts
2092
+ * defaultResolvePath(
2093
+ * { baseName: 'petTypes.ts', path: '/pets/list' },
2094
+ * { root: '/src', output: { path: 'types' }, group: { type: 'path' } },
2095
+ * )
2096
+ * // → '/src/types/pets/petTypes.ts'
2097
+ * ```
2098
+ *
2099
+ * @example Single-file mode
2100
+ * ```ts
2101
+ * defaultResolvePath(
2102
+ * { baseName: 'petTypes.ts', pathMode: 'single' },
2103
+ * { root: '/src', output: { path: 'types' } },
2104
+ * )
2105
+ * // → '/src/types'
2106
+ * ```
2107
+ */
2108
+ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root, output, group }) {
2109
+ if ((pathMode ?? getMode(node_path.default.resolve(root, output.path))) === "single") return node_path.default.resolve(root, output.path);
2110
+ if (group && (groupPath || tag)) {
2111
+ const groupName = group.name ? group.name : (ctx) => {
2112
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
2113
+ return `${camelCase(ctx.group)}Controller`;
2114
+ };
2115
+ return node_path.default.resolve(root, output.path, groupName({ group: group.type === "path" ? groupPath : tag }), baseName);
2116
+ }
2117
+ return node_path.default.resolve(root, output.path, baseName);
2118
+ }
2119
+ /**
2120
+ * Default file resolver used by `defineResolver`.
2121
+ *
2122
+ * Resolves a `FabricFile.File` by combining name resolution (`resolver.default`) with
2123
+ * path resolution (`resolver.resolvePath`). The resolved file always has empty
2124
+ * `sources`, `imports`, and `exports` arrays — consumers populate those separately.
2125
+ *
2126
+ * In `single` mode the name is omitted and the file sits directly in the output directory.
2127
+ *
2128
+ * @example Resolve a schema file
2129
+ * ```ts
2130
+ * const file = defaultResolveFile.call(resolver,
2131
+ * { name: 'pet', extname: '.ts' },
2132
+ * { root: '/src', output: { path: 'types' } },
2133
+ * )
2134
+ * // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
2135
+ * ```
2136
+ *
2137
+ * @example Resolve an operation file with tag grouping
2138
+ * ```ts
2139
+ * const file = defaultResolveFile.call(resolver,
2140
+ * { name: 'listPets', extname: '.ts', tag: 'pets' },
2141
+ * { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
2142
+ * )
2143
+ * // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
2144
+ * ```
2145
+ */
2146
+ function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
2147
+ const pathMode = getMode(node_path.default.resolve(context.root, context.output.path));
2148
+ const baseName = `${pathMode === "single" ? "" : this.default(name, "file")}${extname}`;
2149
+ const filePath = this.resolvePath({
2150
+ baseName,
2151
+ pathMode,
2152
+ tag,
2153
+ path: groupPath
2154
+ }, context);
2155
+ return {
2156
+ path: filePath,
2157
+ baseName: node_path.default.basename(filePath),
2158
+ meta: { pluginName: this.pluginName },
2159
+ sources: [],
2160
+ imports: [],
2161
+ exports: []
2162
+ };
2163
+ }
2164
+ /**
2165
+ * Generates the default "Generated by Kubb" banner from config and optional node metadata.
2166
+ */
2167
+ function buildDefaultBanner({ title, description, version, config }) {
2168
+ try {
2169
+ let source = "";
2170
+ if (Array.isArray(config.input)) {
2171
+ const first = config.input[0];
2172
+ if (first && "path" in first) source = node_path.default.basename(first.path);
2173
+ } else if ("path" in config.input) source = node_path.default.basename(config.input.path);
2174
+ else if ("data" in config.input) source = "text content";
2175
+ let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
2176
+ if (config.output.defaultBanner === "simple") {
2177
+ banner += "*/\n";
2178
+ return banner;
2179
+ }
2180
+ if (source) banner += `* Source: ${source}\n`;
2181
+ if (title) banner += `* Title: ${title}\n`;
2182
+ if (description) {
2183
+ const formattedDescription = description.replace(/\n/gm, "\n* ");
2184
+ banner += `* Description: ${formattedDescription}\n`;
2185
+ }
2186
+ if (version) banner += `* OpenAPI spec version: ${version}\n`;
2187
+ banner += "*/\n";
2188
+ return banner;
2189
+ } catch (_error) {
2190
+ return "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n*/";
2191
+ }
2192
+ }
2193
+ /**
2194
+ * Default banner resolver — returns the banner string for a generated file.
2195
+ *
2196
+ * - When `output.banner` is a function and `node` is provided, calls it with the node.
2197
+ * - When `output.banner` is a function and `node` is absent, falls back to the default Kubb banner.
2198
+ * - When `output.banner` is a string, returns it directly.
2199
+ * - When `config.output.defaultBanner` is `false`, returns `undefined`.
2200
+ * - Otherwise returns the default "Generated by Kubb" banner.
2201
+ *
2202
+ * @example String banner
2203
+ * ```ts
2204
+ * defaultResolveBanner(undefined, { output: { banner: '// my banner' }, config })
2205
+ * // → '// my banner'
2206
+ * ```
2207
+ *
2208
+ * @example Function banner with node
2209
+ * ```ts
2210
+ * defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
2211
+ * // → '// v3.0.0'
2212
+ * ```
2213
+ *
2214
+ * @example Disabled banner
2215
+ * ```ts
2216
+ * defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
2217
+ * // → undefined
2218
+ * ```
2219
+ */
2220
+ function defaultResolveBanner(node, { output, config }) {
2221
+ if (typeof output?.banner === "function") return node ? output.banner(node) : buildDefaultBanner({ config });
2222
+ if (typeof output?.banner === "string") return output.banner;
2223
+ if (config.output.defaultBanner === false) return;
2224
+ return buildDefaultBanner({ config });
2225
+ }
2226
+ /**
2227
+ * Default footer resolver — returns the footer string for a generated file.
2228
+ *
2229
+ * - When `output.footer` is a function and `node` is provided, calls it with the node.
2230
+ * - When `output.footer` is a function and `node` is absent, returns `undefined`.
2231
+ * - When `output.footer` is a string, returns it directly.
2232
+ * - Otherwise returns `undefined`.
2233
+ *
2234
+ * @example String footer
2235
+ * ```ts
2236
+ * defaultResolveFooter(undefined, { output: { footer: '// end of file' }, config })
2237
+ * // → '// end of file'
2238
+ * ```
2239
+ *
2240
+ * @example Function footer with node
2241
+ * ```ts
2242
+ * defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
2243
+ * // → '// Pet Store'
2244
+ * ```
2245
+ */
2246
+ function defaultResolveFooter(node, { output }) {
2247
+ if (typeof output?.footer === "function") return node ? output.footer(node) : void 0;
2248
+ if (typeof output?.footer === "string") return output.footer;
2249
+ }
2250
+ /**
2251
+ * Defines a resolver for a plugin, injecting built-in defaults for name casing,
2252
+ * include/exclude/override filtering, path resolution, and file construction.
2253
+ *
2254
+ * All four defaults can be overridden by providing them in the builder function:
2255
+ * - `default` — name casing strategy (camelCase / PascalCase)
2256
+ * - `resolveOptions` — include/exclude/override filtering
2257
+ * - `resolvePath` — output path computation
2258
+ * - `resolveFile` — full `FabricFile.File` construction
2259
+ *
2260
+ * Methods in the builder have access to `this` (the full resolver object), so they
2261
+ * can call other resolver methods without circular imports.
2262
+ *
2263
+ * @example Basic resolver with naming helpers
2264
+ * ```ts
2011
2265
  * export const resolver = defineResolver<PluginTs>(() => ({
2012
2266
  * name: 'default',
2013
- * resolveName(name) {
2014
- * return this.default(name, 'function')
2267
+ * resolveName(node) {
2268
+ * return this.default(node.name, 'function')
2015
2269
  * },
2016
- * resolveTypedName(name) {
2017
- * return this.default(name, 'type')
2270
+ * resolveTypedName(node) {
2271
+ * return this.default(node.name, 'type')
2018
2272
  * },
2273
+ * }))
2274
+ * ```
2275
+ *
2276
+ * @example Override resolvePath for a custom output structure
2277
+ * ```ts
2278
+ * export const resolver = defineResolver<PluginTs>(() => ({
2279
+ * name: 'custom',
2280
+ * resolvePath({ baseName }, { root, output }) {
2281
+ * return path.resolve(root, output.path, 'generated', baseName)
2282
+ * },
2283
+ * }))
2284
+ * ```
2285
+ *
2286
+ * @example Use this.default inside a helper
2287
+ * ```ts
2288
+ * export const resolver = defineResolver<PluginTs>(() => ({
2289
+ * name: 'default',
2019
2290
  * resolveParamName(node, param) {
2020
- * return this.resolveName(`${node.operationId} ${param.in} ${param.name}`)
2291
+ * return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
2021
2292
  * },
2022
2293
  * }))
2294
+ * ```
2023
2295
  */
2024
2296
  function defineResolver(build) {
2025
2297
  return {
2026
2298
  default: defaultResolver,
2027
2299
  resolveOptions: defaultResolveOptions,
2300
+ resolvePath: defaultResolvePath,
2301
+ resolveFile: defaultResolveFile,
2302
+ resolveBanner: defaultResolveBanner,
2303
+ resolveFooter: defaultResolveFooter,
2028
2304
  ...build()
2029
2305
  };
2030
2306
  }
@@ -2034,13 +2310,17 @@ function defineResolver(build) {
2034
2310
  * Renders a React component for a list of operation nodes (V2 generators).
2035
2311
  */
2036
2312
  async function renderOperations(nodes, options) {
2037
- const { config, fabric, plugin, Component, adapter } = options;
2313
+ const { config, fabric, plugin, Component, driver, adapter } = options;
2038
2314
  if (!Component) return;
2039
2315
  const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2040
2316
  await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, {
2041
- meta: { plugin },
2317
+ meta: {
2318
+ plugin,
2319
+ driver
2320
+ },
2042
2321
  children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2043
2322
  config,
2323
+ plugin,
2044
2324
  adapter,
2045
2325
  nodes,
2046
2326
  options: options.options
@@ -2053,17 +2333,17 @@ async function renderOperations(nodes, options) {
2053
2333
  * Renders a React component for a single operation node (V2 generators).
2054
2334
  */
2055
2335
  async function renderOperation(node, options) {
2056
- const { config, fabric, plugin, Component, adapter, driver, mode } = options;
2336
+ const { config, fabric, plugin, Component, adapter, driver } = options;
2057
2337
  if (!Component) return;
2058
2338
  const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2059
2339
  await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, {
2060
2340
  meta: {
2061
2341
  plugin,
2062
- driver,
2063
- mode
2342
+ driver
2064
2343
  },
2065
2344
  children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2066
2345
  config,
2346
+ plugin,
2067
2347
  adapter,
2068
2348
  node,
2069
2349
  options: options.options
@@ -2076,17 +2356,17 @@ async function renderOperation(node, options) {
2076
2356
  * Renders a React component for a single schema node (V2 generators).
2077
2357
  */
2078
2358
  async function renderSchema(node, options) {
2079
- const { config, fabric, plugin, Component, adapter, driver, mode } = options;
2359
+ const { config, fabric, plugin, Component, adapter, driver } = options;
2080
2360
  if (!Component) return;
2081
2361
  const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
2082
2362
  await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, {
2083
2363
  meta: {
2084
2364
  plugin,
2085
- driver,
2086
- mode
2365
+ driver
2087
2366
  },
2088
2367
  children: /* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(Component, {
2089
2368
  config,
2369
+ plugin,
2090
2370
  adapter,
2091
2371
  node,
2092
2372
  options: options.options
@@ -2262,7 +2542,7 @@ async function detectFormatter() {
2262
2542
  //#region src/utils/TreeNode.ts
2263
2543
  /**
2264
2544
  * Tree structure used to build per-directory barrel (`index.ts`) files from a
2265
- * flat list of generated {@link KubbFile.File} entries.
2545
+ * flat list of generated {@link FabricFile.File} entries.
2266
2546
  *
2267
2547
  * Each node represents either a directory or a file within the output tree.
2268
2548
  * Use {@link TreeNode.build} to construct a root node from a file list, then
@@ -2304,24 +2584,39 @@ var TreeNode = class TreeNode {
2304
2584
  this.#cachedLeaves = leaves;
2305
2585
  return leaves;
2306
2586
  }
2587
+ /**
2588
+ * Visits this node and every descendant in depth-first order.
2589
+ */
2307
2590
  forEach(callback) {
2308
2591
  if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2309
2592
  callback(this);
2310
2593
  for (const child of this.children) child.forEach(callback);
2311
2594
  return this;
2312
2595
  }
2596
+ /**
2597
+ * Finds the first leaf that satisfies `predicate`, or `undefined` when none match.
2598
+ */
2313
2599
  findDeep(predicate) {
2314
2600
  if (typeof predicate !== "function") throw new TypeError("find() predicate must be a function");
2315
2601
  return this.leaves.find(predicate);
2316
2602
  }
2603
+ /**
2604
+ * Calls `callback` for every leaf of this node.
2605
+ */
2317
2606
  forEachDeep(callback) {
2318
2607
  if (typeof callback !== "function") throw new TypeError("forEach() callback must be a function");
2319
2608
  this.leaves.forEach(callback);
2320
2609
  }
2610
+ /**
2611
+ * Returns all leaves that satisfy `callback`.
2612
+ */
2321
2613
  filterDeep(callback) {
2322
2614
  if (typeof callback !== "function") throw new TypeError("filter() callback must be a function");
2323
2615
  return this.leaves.filter(callback);
2324
2616
  }
2617
+ /**
2618
+ * Maps every leaf through `callback` and returns the resulting array.
2619
+ */
2325
2620
  mapDeep(callback) {
2326
2621
  if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
2327
2622
  return this.leaves.map(callback);
@@ -2407,7 +2702,7 @@ function buildDirectoryTree(files, rootFolder = "") {
2407
2702
  function getBarrelFilesByRoot(root, files) {
2408
2703
  const cachedFiles = /* @__PURE__ */ new Map();
2409
2704
  TreeNode.build(files, root)?.forEach((treeNode) => {
2410
- if (!treeNode || !treeNode.children || !treeNode.parent?.data.path) return;
2705
+ if (!treeNode?.children || !treeNode.parent?.data.path) return;
2411
2706
  const barrelFile = {
2412
2707
  path: (0, node_path.join)(treeNode.parent?.data.path, "index.ts"),
2413
2708
  baseName: "index.ts",
@@ -2494,7 +2789,15 @@ async function getConfigs(config, args) {
2494
2789
  //#endregion
2495
2790
  //#region src/utils/mergeResolvers.ts
2496
2791
  /**
2497
- * Merges an array of resolvers into a single resolver. Later entries override earlier ones (last wins).
2792
+ * Merges an ordered list of resolvers into a single resolver by shallow-merging each entry left to right.
2793
+ *
2794
+ * Later entries win when keys conflict, so the last resolver in the list takes highest precedence.
2795
+ *
2796
+ * @example
2797
+ * ```ts
2798
+ * const resolver = mergeResolvers(resolverTs, resolverTsLegacy)
2799
+ * // resolverTsLegacy methods override resolverTs where they overlap
2800
+ * ```
2498
2801
  */
2499
2802
  function mergeResolvers(...resolvers) {
2500
2803
  return resolvers.reduce((acc, curr) => ({
@@ -2505,21 +2808,25 @@ function mergeResolvers(...resolvers) {
2505
2808
  //#endregion
2506
2809
  //#region src/utils/getPreset.ts
2507
2810
  /**
2508
- * Resolves a named preset into merged resolvers and transformers.
2811
+ * Resolves a named preset into merged resolvers, transformers, and generators.
2509
2812
  *
2510
- * - Merges the preset's resolvers on top of the first (default) resolver to produce `baseResolver`.
2813
+ * - Merges the preset's resolvers on top of the first (default)
2511
2814
  * - Merges any additional user-supplied resolvers on top of that to produce the final `resolver`.
2512
2815
  * - Concatenates preset transformers before user-supplied transformers.
2816
+ * - Combines preset generators with user-supplied generators; falls back to the `default` preset's generators when neither provides any.
2513
2817
  */
2514
2818
  function getPreset(params) {
2515
- const { preset: presetName, presets, resolvers, transformers: userTransformers } = params;
2819
+ const { preset: presetName, presets, resolvers, transformers: userTransformers, generators: userGenerators } = params;
2516
2820
  const [defaultResolver, ...userResolvers] = resolvers;
2517
2821
  const preset = presets[presetName];
2518
- const baseResolver = mergeResolvers(defaultResolver, ...preset?.resolvers ?? []);
2822
+ const resolver = mergeResolvers(mergeResolvers(defaultResolver, ...preset?.resolvers ?? []), ...userResolvers ?? []);
2823
+ const transformers = [...preset?.transformers ?? [], ...userTransformers ?? []];
2824
+ const presetGenerators = preset?.generators ?? [];
2825
+ const defaultPresetGenerators = presets["default"]?.generators ?? [];
2519
2826
  return {
2520
- baseResolver,
2521
- resolver: mergeResolvers(baseResolver, ...userResolvers ?? []),
2522
- transformers: [...preset?.transformers ?? [], ...userTransformers ?? []],
2827
+ resolver,
2828
+ transformers,
2829
+ generators: presetGenerators.length > 0 || userGenerators.length ? [...presetGenerators, ...userGenerators] : defaultPresetGenerators,
2523
2830
  preset
2524
2831
  };
2525
2832
  }
@@ -2611,11 +2918,17 @@ exports.FunctionParams = FunctionParams;
2611
2918
  exports.PluginDriver = PluginDriver;
2612
2919
  exports.URLPath = URLPath;
2613
2920
  exports.build = build;
2921
+ exports.buildDefaultBanner = buildDefaultBanner;
2614
2922
  exports.createAdapter = createAdapter;
2615
2923
  exports.createPlugin = createPlugin;
2616
2924
  exports.createStorage = createStorage;
2617
2925
  exports.default = build;
2926
+ exports.defaultResolveBanner = defaultResolveBanner;
2927
+ exports.defaultResolveFile = defaultResolveFile;
2928
+ exports.defaultResolveFooter = defaultResolveFooter;
2618
2929
  exports.defaultResolveOptions = defaultResolveOptions;
2930
+ exports.defaultResolvePath = defaultResolvePath;
2931
+ exports.defineBuilder = defineBuilder;
2619
2932
  exports.defineConfig = defineConfig;
2620
2933
  exports.defineGenerator = defineGenerator;
2621
2934
  exports.defineLogger = defineLogger;