@kubb/core 5.12.7 → 5.13.0

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.
@@ -846,9 +846,13 @@ declare class FileManager {
846
846
  add(...files: Array<FileNode>): Array<FileNode>;
847
847
  upsert(...files: Array<FileNode>): Array<FileNode>;
848
848
  /** Stores a generator result: a file array is upserted as is, an element renders through `renderer` first, falsy is a no-op. */
849
- dispatch<TElement = unknown>({ result, renderer }: {
849
+ dispatch<TElement = unknown>({ result, renderer, pointer }: {
850
850
  result: TElement | Array<FileNode> | undefined | null;
851
851
  renderer?: RendererFactory<TElement> | null;
852
+ /**
853
+ * Pointer of the node the result was generated from, set on each source that carries none.
854
+ */
855
+ pointer?: string;
852
856
  }): Promise<void>;
853
857
  /**
854
858
  * Releases all stored files. Called by the core after `kubb:build:end`.
@@ -3183,6 +3187,11 @@ type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions> = {
3183
3187
  validate: (input: string, options?: {
3184
3188
  throwOnError?: boolean;
3185
3189
  }) => Promise<void>;
3190
+ /**
3191
+ * Waits for work `parse` left running alongside generation, such as spec validation in a worker thread.
3192
+ * Kubb awaits it before `kubb:build:end`, inside the build's diagnostics scope. Must not reject.
3193
+ */
3194
+ settle?: () => Promise<void>;
3186
3195
  /**
3187
3196
  * Zero-based UTF-16 offsets of a diagnostic in `source` for a code frame, `end` exclusive; `undefined` when it cannot be found.
3188
3197
  */
@@ -3223,4 +3232,4 @@ type AdapterFactory<T extends AdapterFactoryOptions = AdapterFactoryOptions> = E
3223
3232
  declare function createAdapter<T extends AdapterFactoryOptions = AdapterFactoryOptions>(definition: AdapterDefinition<T>): AdapterFactory<T>;
3224
3233
  //#endregion
3225
3234
  export { NodeCache as $, ReporterContext as $t, PluginFactory as A, KubbInfoContext as At, ResolveImportsOptions as B, RunHookOptions as Bt, NormalizedPlugin as C, KubbGenerationEndContext as Ct, Override as D, KubbHookLineContext as Dt, OutputOptions as E, KubbHookEndContext as Et, definePlugin as F, KubbWarnContext as Ft, ResolverFile as G, createOutputManifest as Gt, ResolvePathOptions as H, KubbDriver as Ht, BannerMeta as I, PossibleConfig as It, ResolverPatch as J, createRenderer as Jt, ResolverFileParams as K, Renderer as Kt, ResolveBannerContext as L, PostGenerateCommand as Lt, PluginType as M, KubbMessageContext as Mt, ResolvePluginOptions as N, KubbPluginsEndContext as Nt, Plugin as O, KubbHookStartContext as Ot, assertPluginOption as P, KubbSuccessContext as Pt, defineGenerator as Q, Reporter as Qt, ResolveBannerFile as R, UserConfig as Rt, KubbPluginStartContext as S, KubbFilesProcessingUpdateContext as St, OutputMode as T, KubbGenerationSummaryContext as Tt, Resolver as U, Hookable as Ut, ResolveOptionsContext as V, runHook as Vt, ResolverDefault as W, OutputManifest as Wt, Generator as X, createStorage as Xt, ResolverPathParams as Y, Storage as Yt, GeneratorContext as Z, GenerationResult as Zt, Filter as _, KubbDiagnosticContext as _t, AdapterSource as a, DiagnosticFormatOptions as an, Diagnostics as at, KubbPluginEndContext as b, KubbFilesProcessingEndContext as bt, ExtensionDependency as c, ParserDefinition as cn, ProblemDiagnostic as ct, ExtensionKind as d, CLIOptions as dt, ReporterName as en, Diagnostic as et, ExtensionMeta as f, Config as ft, Exclude$1 as g, KubbBuildStartContext as gt, resolveMeta as h, KubbBuildEndContext as ht, AdapterFactoryOptions as i, logLevel as in, DiagnosticSeverity as it, PluginName as j, KubbLifecycleStartContext as jt, PluginDefinition as k, KubbHooks as kt, ExtensionDependencyRecord as l, ParserFactory as ln, UpdateDiagnostic as lt, defineExtension as m, Input as mt, AdapterDefinition as n, UserReporter as nn, DiagnosticKind as nt, createAdapter as o, SerializedDiagnostic as on, PerformanceDiagnostic as ot, checkCompatibility as p, FilesContext as pt, ResolverFilePathParams as q, RendererFactory as qt, AdapterFactory as r, createReporter as rn, DiagnosticLocation as rt, ExtensionDependencies as s, Parser as sn, ProblemCode as st, Adapter as t, ReporterPluginFiles as tn, DiagnosticDoc as tt, ExtensionFactory as u, defineParser as un, BuildOutput as ut, Group as v, KubbErrorContext as vt, Output as w, KubbGenerationStartContext as wt, KubbPluginSetupContext as x, KubbFilesProcessingStartContext as xt, Include as y, KubbFileProcessingUpdate as yt, ResolveFileOptions as z, HookResult as zt };
3226
- //# sourceMappingURL=createAdapter-DFXvZLZC.d.ts.map
3235
+ //# sourceMappingURL=createAdapter-BNUEQcw9.d.ts.map
package/dist/index.cjs CHANGED
@@ -39,13 +39,10 @@ node_net = __toESM(node_net, 1);
39
39
  let node_os = require("node:os");
40
40
  let node_crypto = require("node:crypto");
41
41
  let node_readline = require("node:readline");
42
- let unstorage = require("unstorage");
43
- let unstorage_drivers_fs = require("unstorage/drivers/fs");
44
- unstorage_drivers_fs = __toESM(unstorage_drivers_fs, 1);
45
42
  let node_url = require("node:url");
46
43
  let node_module = require("node:module");
47
44
  //#region package.json
48
- var version = "5.12.7";
45
+ var version = "5.13.0";
49
46
  //#endregion
50
47
  //#region ../../internals/utils/src/casing.ts
51
48
  /**
@@ -138,20 +135,6 @@ function isCIEnvironment() {
138
135
  return !!(ci && ci !== "false" && ci !== "0" || node_process.default.env.GITHUB_ACTIONS || node_process.default.env.GITLAB_CI || node_process.default.env.BITBUCKET_BUILD_NUMBER || node_process.default.env.JENKINS_URL || node_process.default.env.CIRCLECI || node_process.default.env.TRAVIS || node_process.default.env.TEAMCITY_VERSION || node_process.default.env.BUILDKITE || node_process.default.env.TF_BUILD);
139
136
  }
140
137
  //#endregion
141
- //#region ../../internals/utils/src/jsonPointer.ts
142
- /**
143
- * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
144
- * name holding those characters maps to a distinct pointer.
145
- *
146
- * @example
147
- * ```ts
148
- * escapePointerToken('/pets/{id}') // '~1pets~1{id}'
149
- * ```
150
- */
151
- function escapePointerToken(token) {
152
- return token.replaceAll("~", "~0").replaceAll("/", "~1");
153
- }
154
- //#endregion
155
138
  //#region ../../internals/utils/src/openInBrowser.ts
156
139
  const ignore = () => {};
157
140
  /**
@@ -203,6 +186,21 @@ function detect() {
203
186
  //#endregion
204
187
  //#region ../../internals/utils/src/fs.ts
205
188
  /**
189
+ * Resolves to `true` when the file or directory at `path` exists.
190
+ * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * if (await exists('./kubb.config.ts')) {
195
+ * const content = await read('./kubb.config.ts')
196
+ * }
197
+ * ```
198
+ */
199
+ async function exists(path) {
200
+ if (runtime.isBun) return Bun.file(path).exists();
201
+ return (0, node_fs_promises.access)(path).then(() => true, () => false);
202
+ }
203
+ /**
206
204
  * Reads the file at `path` as a UTF-8 string.
207
205
  * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
208
206
  *
@@ -1842,13 +1840,13 @@ var Resolver = class Resolver {
1842
1840
  const resolveName = name ?? ((schemaName) => this.name(schemaName));
1843
1841
  return (0, _kubb_ast.collectImportedRefNames)(node).map((schemaName) => _kubb_ast.ast.factory.createImport({
1844
1842
  name: [resolveName(schemaName)],
1845
- path: this.file({
1843
+ path: this.#resolveFilePath({
1846
1844
  name: schemaName,
1847
1845
  extname,
1848
1846
  root,
1849
1847
  output,
1850
1848
  group
1851
- }).path
1849
+ })
1852
1850
  }));
1853
1851
  }
1854
1852
  /**
@@ -2007,19 +2005,13 @@ var Resolver = class Resolver {
2007
2005
  });
2008
2006
  return resolved;
2009
2007
  }
2010
- /**
2011
- * Builds a `FileNode`. When `#filePath` (the resolver's `file.path`) is set it owns the whole
2012
- * path; otherwise the base name (from `#baseName`, the resolver's `file.baseName` or the
2013
- * built-in `toBaseName`) is placed by the `output.path`/`group` layout. The resolved file starts
2014
- * with empty `sources`, `imports`, and `exports`, which consumers populate separately.
2015
- */
2016
- #resolveFile(options) {
2017
- const { name, extname, tag, path: groupPath, root, output, group } = options;
2008
+ /** The absolute path `#resolveFile` places a file at. */
2009
+ #resolveFilePath({ name, extname, tag, path: groupPath, root, output, group }) {
2018
2010
  const baseName = this.#baseName({
2019
2011
  name,
2020
2012
  extname
2021
2013
  });
2022
- const filePath = this.#filePath ? this.#resolveOverridePath(this.#filePath({
2014
+ return this.#filePath ? this.#resolveOverridePath(this.#filePath({
2023
2015
  baseName,
2024
2016
  output
2025
2017
  }), root) : this.#resolvePath({
@@ -2030,6 +2022,15 @@ var Resolver = class Resolver {
2030
2022
  output,
2031
2023
  group
2032
2024
  });
2025
+ }
2026
+ /**
2027
+ * Builds a `FileNode`. When `#filePath` (the resolver's `file.path`) is set it owns the whole
2028
+ * path; otherwise the base name (from `#baseName`, the resolver's `file.baseName` or the
2029
+ * built-in `toBaseName`) is placed by the `output.path`/`group` layout. The resolved file starts
2030
+ * with empty `sources`, `imports`, and `exports`, which consumers populate separately.
2031
+ */
2032
+ #resolveFile(options) {
2033
+ const filePath = this.#resolveFilePath(options);
2033
2034
  return _kubb_ast.ast.factory.createFile({
2034
2035
  path: filePath,
2035
2036
  baseName: node_path.default.basename(filePath),
@@ -2301,6 +2302,17 @@ function createProgress(files, config, hooks) {
2301
2302
  if (updates.length) await hooks.callHook("kubb:files:processing:update", { files: updates });
2302
2303
  };
2303
2304
  }
2305
+ /** Links each source without a pointer to the node its file was generated from. */
2306
+ function withPointer(files, pointer) {
2307
+ if (!pointer) return files;
2308
+ return files.map((file) => ({
2309
+ ...file,
2310
+ sources: file.sources.map((source) => source.pointer ? source : {
2311
+ ...source,
2312
+ pointer
2313
+ })
2314
+ }));
2315
+ }
2304
2316
  /**
2305
2317
  * In-memory file store for generated files, and the writer that turns them into source
2306
2318
  * strings on `storage`. Files sharing a `path` are merged (sources/imports/exports
@@ -2319,6 +2331,7 @@ var FileManager = class {
2319
2331
  #cache = /* @__PURE__ */ new Map();
2320
2332
  #pending = /* @__PURE__ */ new Set();
2321
2333
  #sorted = null;
2334
+ #merges = /* @__PURE__ */ new WeakSet();
2322
2335
  add(...files) {
2323
2336
  return this.#store(files, false);
2324
2337
  }
@@ -2326,18 +2339,18 @@ var FileManager = class {
2326
2339
  return this.#store(files, true);
2327
2340
  }
2328
2341
  /** Stores a generator result: a file array is upserted as is, an element renders through `renderer` first, falsy is a no-op. */
2329
- async dispatch({ result, renderer }) {
2342
+ async dispatch({ result, renderer, pointer }) {
2330
2343
  try {
2331
2344
  var _usingCtx$2 = _usingCtx();
2332
2345
  if (!result) return;
2333
2346
  if (Array.isArray(result)) {
2334
- this.#defer(result);
2347
+ this.#defer(withPointer(result, pointer));
2335
2348
  return;
2336
2349
  }
2337
2350
  if (!renderer) return;
2338
2351
  const instance = _usingCtx$2.u(renderer());
2339
2352
  await instance.render(result);
2340
- this.#defer(instance.files);
2353
+ this.#defer(withPointer(instance.files, pointer));
2341
2354
  } catch (_) {
2342
2355
  _usingCtx$2.e = _;
2343
2356
  } finally {
@@ -2347,9 +2360,24 @@ var FileManager = class {
2347
2360
  /** Upserts without normalizing: the raw merge waits in `#pending` until the file is read. */
2348
2361
  #defer(files) {
2349
2362
  for (const file of files) {
2350
- const existing = this.#cache.get(file.path);
2351
- this.#cache.set(file.path, existing ? mergeFile(existing, file) : file);
2352
2363
  this.#pending.add(file.path);
2364
+ const existing = this.#cache.get(file.path);
2365
+ if (!existing) {
2366
+ this.#cache.set(file.path, file);
2367
+ continue;
2368
+ }
2369
+ if (!this.#merges.has(existing)) {
2370
+ const merged = mergeFile(existing, file);
2371
+ this.#merges.add(merged);
2372
+ this.#cache.set(file.path, merged);
2373
+ continue;
2374
+ }
2375
+ existing.banner = file.banner;
2376
+ existing.footer = file.footer;
2377
+ existing.copy = file.copy ?? existing.copy;
2378
+ existing.sources.push(...file.sources);
2379
+ existing.imports.push(...file.imports);
2380
+ existing.exports.push(...file.exports);
2353
2381
  }
2354
2382
  if (files.length > 0) this.#sorted = null;
2355
2383
  }
@@ -2528,24 +2556,23 @@ function assertPluginEntry(entry, index) {
2528
2556
  });
2529
2557
  }
2530
2558
  /**
2531
- * Where a generator crashed: a label naming the node and, when the node has one, a JSON pointer
2532
- * to it. Built from the node fields so core stays adapter-agnostic.
2559
+ * Where a generator crashed: a label naming the node and, when the adapter set one, the node's
2560
+ * pointer into the input.
2533
2561
  */
2534
2562
  function getCrashSite(node) {
2535
- if (node.kind === "Schema") return node.name ? {
2536
- label: `schema "${node.name}"`,
2537
- location: {
2563
+ if (node.kind === "Schema") return {
2564
+ label: node.name ? `schema "${node.name}"` : "an inline schema",
2565
+ location: node.pointer ? {
2538
2566
  kind: "schema",
2539
- pointer: `#/components/schemas/${escapePointerToken(node.name)}`
2540
- }
2541
- } : { label: "an inline schema" };
2542
- if (!node.method || !node.path) return { label: `operation "${node.operationId}"` };
2567
+ pointer: node.pointer
2568
+ } : void 0
2569
+ };
2543
2570
  return {
2544
- label: `operation "${node.operationId}" (${node.method.toUpperCase()} ${node.path})`,
2545
- location: {
2571
+ label: node.method && node.path ? `operation "${node.operationId}" (${node.method.toUpperCase()} ${node.path})` : `operation "${node.operationId}"`,
2572
+ location: node.pointer ? {
2546
2573
  kind: "operation",
2547
- pointer: `#/paths/${escapePointerToken(node.path)}/${node.method.toLowerCase()}`
2548
- }
2574
+ pointer: node.pointer
2575
+ } : void 0
2549
2576
  };
2550
2577
  }
2551
2578
  var KubbDriver = class {
@@ -2833,6 +2860,7 @@ var KubbDriver = class {
2833
2860
  hooks,
2834
2861
  config
2835
2862
  });
2863
+ await this.adapter?.settle?.();
2836
2864
  await hooks.callHook("kubb:build:end", {
2837
2865
  files: this.fileManager.files,
2838
2866
  config,
@@ -2840,6 +2868,7 @@ var KubbDriver = class {
2840
2868
  });
2841
2869
  return { diagnostics: Diagnostics.dedupe(diagnostics) };
2842
2870
  } catch (caughtError) {
2871
+ await this.adapter?.settle?.();
2843
2872
  diagnostics.push(Diagnostics.from(caughtError));
2844
2873
  return { diagnostics: Diagnostics.dedupe(diagnostics) };
2845
2874
  }
@@ -3068,7 +3097,8 @@ var KubbDriver = class {
3068
3097
  if (!(generator.match ? await generator.match(node, ctx) : true)) continue;
3069
3098
  await this.fileManager.dispatch({
3070
3099
  result: await generate(generator),
3071
- renderer: generator.renderer
3100
+ renderer: generator.renderer,
3101
+ pointer: node.pointer
3072
3102
  });
3073
3103
  }
3074
3104
  }
@@ -4143,8 +4173,9 @@ function resolveKubbDir(base = "") {
4143
4173
  }
4144
4174
  }
4145
4175
  /**
4146
- * unstorage on the fs driver, rooted at {@link resolveKubbDir}. The fs driver cannot set a file
4147
- * mode, so the 0700 folder keeps files such as a stored token private.
4176
+ * Key-value files rooted at {@link resolveKubbDir}. Each key is a file path inside that folder.
4177
+ * Strings are stored as is and other values as JSON. `getItem` parses JSON and falls back to the
4178
+ * raw string. The 0700 folder keeps files such as a stored token private.
4148
4179
  *
4149
4180
  * @example
4150
4181
  * ```ts
@@ -4153,7 +4184,30 @@ function resolveKubbDir(base = "") {
4153
4184
  * ```
4154
4185
  */
4155
4186
  function kubbStorage({ base } = {}) {
4156
- return (0, unstorage.createStorage)({ driver: (0, unstorage_drivers_fs.default)({ base: resolveKubbDir(base) }) });
4187
+ const dir = resolveKubbDir(base);
4188
+ const pathOf = (key) => {
4189
+ const path = (0, node_path.resolve)(dir, key.replaceAll(":", "/"));
4190
+ if (path === dir || !isPathInside(path, dir)) throw new Error(`Invalid key: ${JSON.stringify(key)}. It should not leave the storage folder`);
4191
+ return path;
4192
+ };
4193
+ return {
4194
+ async getItem(key) {
4195
+ const path = pathOf(key);
4196
+ if (!await exists(path)) return null;
4197
+ const raw = await read(path);
4198
+ try {
4199
+ return JSON.parse(raw);
4200
+ } catch {
4201
+ return raw;
4202
+ }
4203
+ },
4204
+ async setItem(key, value) {
4205
+ await writeRaw(pathOf(key), typeof value === "string" ? value : JSON.stringify(value));
4206
+ },
4207
+ async removeItem(key) {
4208
+ await (0, node_fs_promises.rm)(pathOf(key), { force: true });
4209
+ }
4210
+ };
4157
4211
  }
4158
4212
  //#endregion
4159
4213
  //#region src/reporters/fileReporter.ts