@kubb/core 5.0.0-beta.41 → 5.0.0-beta.42

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.
@@ -238,6 +238,83 @@ type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) =
238
238
  */
239
239
  declare function createAdapter<T extends AdapterFactoryOptions = AdapterFactoryOptions>(build: AdapterBuilder<T>): (options?: T['options']) => Adapter<T>;
240
240
  //#endregion
241
+ //#region src/createCache.d.ts
242
+ /**
243
+ * A snapshot of a completed build: the final rendered source of every generated
244
+ * file, keyed by its path relative to the output root. Restoring a snapshot writes
245
+ * those sources straight to storage, skipping generation entirely.
246
+ *
247
+ * Paths are relative, not absolute, so the snapshot never depends on where the
248
+ * project lives on disk.
249
+ */
250
+ type CachedSnapshot = {
251
+ /**
252
+ * Final source per output file, keyed by the path relative to
253
+ * `resolve(config.root, config.output.path)`.
254
+ */
255
+ files: Record<string, string>;
256
+ };
257
+ /**
258
+ * Backend that stores build snapshots for incremental ("hot") rebuilds. When the
259
+ * input fingerprint matches a stored key, Kubb restores the snapshot instead of
260
+ * regenerating. Kubb ships with `fsCache` (local disk). Implement this interface to
261
+ * back the cache with any other store.
262
+ *
263
+ * @see {@link createCache} to build a custom backend.
264
+ */
265
+ type Cache = {
266
+ /**
267
+ * Identifier used in logs and diagnostics (`'fs'`, `'memory'`).
268
+ */
269
+ readonly name: string;
270
+ /**
271
+ * Returns the snapshot stored under `key`, or `null` on a miss. A backend never
272
+ * throws on a miss or a transient failure. It returns `null` so the build falls
273
+ * through to regeneration.
274
+ */
275
+ restore(params: {
276
+ key: string;
277
+ }): Promise<CachedSnapshot | null>;
278
+ /**
279
+ * Stores `snapshot` under `key`. Only called after a successful build with no
280
+ * error diagnostics.
281
+ */
282
+ persist(params: {
283
+ key: string;
284
+ snapshot: CachedSnapshot;
285
+ }): Promise<void>;
286
+ /**
287
+ * Optional teardown called after the build. Use to flush buffers or close
288
+ * connections.
289
+ */
290
+ dispose?(): Promise<void>;
291
+ };
292
+ /**
293
+ * Defines a custom cache backend. The builder receives user options and returns a
294
+ * {@link Cache}. Reach for this when the filesystem backend doesn't fit, for
295
+ * example to store snapshots in Redis or a database.
296
+ *
297
+ * @example In-memory cache (the built-in implementation)
298
+ * ```ts
299
+ * import { createCache } from '@kubb/core'
300
+ *
301
+ * export const memoryCache = createCache(() => {
302
+ * const store = new Map<string, CachedSnapshot>()
303
+ *
304
+ * return {
305
+ * name: 'memory',
306
+ * async restore({ key }) {
307
+ * return store.get(key) ?? null
308
+ * },
309
+ * async persist({ key, snapshot }) {
310
+ * store.set(key, snapshot)
311
+ * },
312
+ * }
313
+ * })
314
+ * ```
315
+ */
316
+ declare function createCache<TOptions = Record<string, never>>(build: (options: TOptions) => Cache): (options?: TOptions) => Cache;
317
+ //#endregion
241
318
  //#region src/constants.d.ts
242
319
  /**
243
320
  * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
@@ -493,6 +570,47 @@ type RendererFactory<TElement = unknown> = () => Renderer<TElement>;
493
570
  */
494
571
  declare function createRenderer<TElement = unknown>(factory: RendererFactory<TElement>): RendererFactory<TElement>;
495
572
  //#endregion
573
+ //#region src/caches/fsCache.d.ts
574
+ /**
575
+ * Options for {@link fsCache}.
576
+ */
577
+ type FsCacheOptions = {
578
+ /**
579
+ * Directory that holds the cache. Resolved against `process.cwd()` when relative.
580
+ *
581
+ * @default 'node_modules/.cache/kubb'
582
+ */
583
+ dir?: string;
584
+ /**
585
+ * Maximum number of build snapshots to keep. The least-recently-used entries are
586
+ * evicted once the cache grows past it.
587
+ *
588
+ * @default 50
589
+ */
590
+ maxEntries?: number;
591
+ /**
592
+ * Days a snapshot may go untouched before it is evicted.
593
+ *
594
+ * @default 7
595
+ */
596
+ ttlDays?: number;
597
+ };
598
+ /**
599
+ * Local filesystem cache. Stores each build snapshot as content blobs plus an index,
600
+ * tracked by a manifest under `node_modules/.cache/kubb/` (the Nx and Vitest
601
+ * convention). Least-recently-used and expired entries are pruned on every persist.
602
+ *
603
+ * @example
604
+ * ```ts
605
+ * import { fsCache } from '@kubb/core'
606
+ *
607
+ * export default defineConfig({
608
+ * cache: fsCache(),
609
+ * })
610
+ * ```
611
+ */
612
+ declare const fsCache: (options?: FsCacheOptions | undefined) => Cache;
613
+ //#endregion
496
614
  //#region src/defineLogger.d.ts
497
615
  /**
498
616
  * Numeric log-level thresholds used internally to compare verbosity.
@@ -679,7 +797,7 @@ type Parser<TMeta extends object = object, TNode = unknown> = {
679
797
  */
680
798
  extNames: Array<FileNode['extname']> | undefined;
681
799
  /**
682
- * Serialise the file's AST into source code.
800
+ * Serialize the file's AST into source code.
683
801
  */
684
802
  parse(file: FileNode<TMeta>, options?: PrintOptions): string;
685
803
  /**
@@ -1976,6 +2094,26 @@ type Config<TInput = Input> = {
1976
2094
  * @see {@link Storage} interface for implementing custom backends.
1977
2095
  */
1978
2096
  storage: Storage;
2097
+ /**
2098
+ * Incremental build cache. Kubb fingerprints the inputs (spec content, config, plugin options,
2099
+ * versions) and, on an unchanged "hot" run, restores the previously generated output instead of
2100
+ * regenerating it. Same idea as Nx's computation cache.
2101
+ *
2102
+ * `defineConfig` enables `fsCache()` (local disk under `node_modules/.cache/kubb`) by default.
2103
+ * Pass another backend to change where snapshots live, or `false` to turn caching off. A bare
2104
+ * `createKubb` leaves it off unless a cache is provided.
2105
+ *
2106
+ * @example
2107
+ * ```ts
2108
+ * import { fsCache } from '@kubb/core'
2109
+ *
2110
+ * cache: fsCache({ dir: '.kubb-cache' })
2111
+ * cache: false
2112
+ * ```
2113
+ *
2114
+ * @see {@link Cache} interface for implementing custom backends.
2115
+ */
2116
+ cache?: Cache;
1979
2117
  /**
1980
2118
  * Plugins that run during the build to generate code and transform the AST. Each one processes
1981
2119
  * the adapter's AST and can emit files for a different target (TypeScript, Zod, Faker). A plugin
@@ -2078,7 +2216,12 @@ type Config<TInput = Input> = {
2078
2216
  * })
2079
2217
  * ```
2080
2218
  */
2081
- type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage' | 'reporters'> & {
2219
+ type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage' | 'reporters' | 'cache'> & {
2220
+ /**
2221
+ * Incremental build cache. Defaults to `fsCache()` (local disk). Pass another {@link Cache}
2222
+ * backend, or `false` to turn caching off.
2223
+ */
2224
+ cache?: Cache | false;
2082
2225
  /**
2083
2226
  * Project root directory, absolute or relative to the config file location.
2084
2227
  * @default process.cwd()
@@ -2383,7 +2526,7 @@ type KubbDiagnosticContext = {
2383
2526
  };
2384
2527
  type KubbFilesProcessingStartContext = {
2385
2528
  /**
2386
- * Files about to be serialised and written.
2529
+ * Files about to be serialized and written.
2387
2530
  */
2388
2531
  files: Array<FileNode>;
2389
2532
  };
@@ -2401,7 +2544,7 @@ type KubbFileProcessingUpdate = {
2401
2544
  */
2402
2545
  percentage: number;
2403
2546
  /**
2404
- * Serialised file content, or `undefined` when the file produced no output.
2547
+ * Serialized file content, or `undefined` when the file produced no output.
2405
2548
  */
2406
2549
  source?: string;
2407
2550
  /**
@@ -2421,7 +2564,7 @@ type KubbFilesProcessingUpdateContext = {
2421
2564
  };
2422
2565
  type KubbFilesProcessingEndContext = {
2423
2566
  /**
2424
- * All files that were serialised in this batch.
2567
+ * All files that were serialized in this batch.
2425
2568
  */
2426
2569
  files: Array<FileNode>;
2427
2570
  };
@@ -2966,5 +3109,5 @@ declare class Diagnostics {
2966
3109
  static formatLines(diagnostic: Diagnostic): Array<string>;
2967
3110
  }
2968
3111
  //#endregion
2969
- export { Output as $, KubbHookEndContext as A, defineLogger as At, UserConfig as B, createAdapter as Bt, KubbErrorContext as C, UserReporter as Ct, KubbFilesProcessingUpdateContext as D, LoggerContext as Dt, KubbFilesProcessingStartContext as E, Logger as Et, KubbLifecycleStartContext as F, Storage as Ft, KubbDriver as G, Generator$1 as H, KubbPluginsEndContext as I, createStorage as It, Include as J, Exclude$1 as K, KubbSuccessContext as L, Adapter as Lt, KubbHookStartContext as M, Renderer as Mt, KubbHooks as N, RendererFactory as Nt, KubbGenerationEndContext as O, LoggerOptions as Ot, KubbInfoContext as P, createRenderer as Pt, NormalizedPlugin as Q, KubbWarnContext as R, AdapterFactoryOptions as Rt, KubbDiagnosticContext as S, ReporterName as St, KubbFilesProcessingEndContext as T, selectReporters as Tt, GeneratorContext as U, createKubb as V, AsyncEventEmitter as Vt, defineGenerator as W, KubbPluginSetupContext as X, KubbPluginEndContext as Y, KubbPluginStartContext as Z, InputPath as _, Parser as _t, DiagnosticLocation as a, ResolveBannerContext as at, KubbBuildStartContext as b, Reporter as bt, PerformanceDiagnostic as c, Resolver as ct, SerializedDiagnostic as d, ResolverPathParams as dt, Override as et, UpdateDiagnostic as f, defineResolver as ft, InputData as g, ParsedFile as gt, Config as h, FileProcessorHooks as ht, DiagnosticKind as i, BannerMeta as it, KubbHookLineContext as j, logLevel as jt, KubbGenerationStartContext as k, UserLogger as kt, ProblemCode as l, ResolverContext as lt, CLIOptions as m, defineMiddleware as mt, DiagnosticByCode as n, PluginFactoryOptions as nt, DiagnosticSeverity as o, ResolveBannerFile as ot, BuildOutput as p, Middleware as pt, Group as q, DiagnosticDoc as r, definePlugin as rt, Diagnostics as s, ResolveOptionsContext as st, Diagnostic as t, Plugin as tt, ProblemDiagnostic as u, ResolverFileParams as ut, Kubb$1 as v, defineParser as vt, KubbFileProcessingUpdate as w, createReporter as wt, KubbConfigEndContext as x, ReporterContext as xt, KubbBuildEndContext as y, GenerationResult as yt, PossibleConfig as z, AdapterSource as zt };
2970
- //# sourceMappingURL=diagnostics-Ba-FcsPo.d.ts.map
3112
+ export { Output as $, KubbHookEndContext as A, defineLogger as At, UserConfig as B, CachedSnapshot as Bt, KubbErrorContext as C, UserReporter as Ct, KubbFilesProcessingUpdateContext as D, LoggerContext as Dt, KubbFilesProcessingStartContext as E, Logger as Et, KubbLifecycleStartContext as F, RendererFactory as Ft, KubbDriver as G, createAdapter as Gt, Generator$1 as H, Adapter as Ht, KubbPluginsEndContext as I, createRenderer as It, Include as J, Exclude$1 as K, AsyncEventEmitter as Kt, KubbSuccessContext as L, Storage as Lt, KubbHookStartContext as M, FsCacheOptions as Mt, KubbHooks as N, fsCache as Nt, KubbGenerationEndContext as O, LoggerOptions as Ot, KubbInfoContext as P, Renderer as Pt, NormalizedPlugin as Q, KubbWarnContext as R, createStorage as Rt, KubbDiagnosticContext as S, ReporterName as St, KubbFilesProcessingEndContext as T, selectReporters as Tt, GeneratorContext as U, AdapterFactoryOptions as Ut, createKubb as V, createCache as Vt, defineGenerator as W, AdapterSource as Wt, KubbPluginSetupContext as X, KubbPluginEndContext as Y, KubbPluginStartContext as Z, InputPath as _, Parser as _t, DiagnosticLocation as a, ResolveBannerContext as at, KubbBuildStartContext as b, Reporter as bt, PerformanceDiagnostic as c, Resolver as ct, SerializedDiagnostic as d, ResolverPathParams as dt, Override as et, UpdateDiagnostic as f, defineResolver as ft, InputData as g, ParsedFile as gt, Config as h, FileProcessorHooks as ht, DiagnosticKind as i, BannerMeta as it, KubbHookLineContext as j, logLevel as jt, KubbGenerationStartContext as k, UserLogger as kt, ProblemCode as l, ResolverContext as lt, CLIOptions as m, defineMiddleware as mt, DiagnosticByCode as n, PluginFactoryOptions as nt, DiagnosticSeverity as o, ResolveBannerFile as ot, BuildOutput as p, Middleware as pt, Group as q, DiagnosticDoc as r, definePlugin as rt, Diagnostics as s, ResolveOptionsContext as st, Diagnostic as t, Plugin as tt, ProblemDiagnostic as u, ResolverFileParams as ut, Kubb$1 as v, defineParser as vt, KubbFileProcessingUpdate as w, createReporter as wt, KubbConfigEndContext as x, ReporterContext as xt, KubbBuildEndContext as y, GenerationResult as yt, PossibleConfig as z, Cache as zt };
3113
+ //# sourceMappingURL=diagnostics-DZGgDzSv.d.ts.map
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_memoryStorage = require("./memoryStorage-DZqlEW7H.cjs");
2
+ const require_memoryStorage = require("./memoryStorage-Dldu8sRT.cjs");
3
3
  let node_util = require("node:util");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_fs_promises = require("node:fs/promises");
@@ -61,7 +61,26 @@ const randomColors = [
61
61
  */
62
62
  function randomCliColor(text) {
63
63
  if (!text) return "";
64
- return (0, node_util.styleText)(randomColors[(0, node_crypto.createHash)("sha256").update(text).digest().readUInt32BE(0) % randomColors.length] ?? "white", text);
64
+ return (0, node_util.styleText)(randomColors[(0, node_crypto.hash)("sha256", text, "buffer").readUInt32BE(0) % randomColors.length] ?? "white", text);
65
+ }
66
+ //#endregion
67
+ //#region ../../internals/utils/src/path.ts
68
+ /**
69
+ * Converts a filesystem path to use POSIX (`/`) separators.
70
+ *
71
+ * Most of the codebase compares and composes paths as strings (prefix matching, joining for
72
+ * import specifiers, splitting on `/`). On POSIX `path.resolve` already returns `/`-separated
73
+ * paths, but on Windows it returns `\`-separated paths, which breaks every such comparison.
74
+ *
75
+ * Routing every path that crosses a module boundary through `toPosixPath` keeps the rest of the
76
+ * code platform-agnostic. The conversion runs unconditionally so Windows-specific behavior is
77
+ * exercisable from POSIX CI.
78
+ *
79
+ * @example
80
+ * toPosixPath('C:\\repo\\src\\pet.ts') // 'C:/repo/src/pet.ts'
81
+ */
82
+ function toPosixPath(filePath) {
83
+ return filePath.replaceAll("\\", "/");
65
84
  }
66
85
  //#endregion
67
86
  //#region ../../internals/utils/src/env.ts
@@ -81,8 +100,72 @@ function isCIEnvironment() {
81
100
  return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.BITBUCKET_BUILD_NUMBER || process.env.JENKINS_URL || process.env.CIRCLECI || process.env.TRAVIS || process.env.TEAMCITY_VERSION || process.env.BUILDKITE || process.env.TF_BUILD);
82
101
  }
83
102
  //#endregion
103
+ //#region ../../internals/utils/src/runtime.ts
104
+ /**
105
+ * Returns `true` when the current process is running under Bun.
106
+ *
107
+ * Detection keys off the global `Bun` object rather than `process.versions`,
108
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
109
+ * otherwise look like Node.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * if (isBun()) {
114
+ * await Bun.write(path, data)
115
+ * }
116
+ * ```
117
+ */
118
+ function isBun() {
119
+ return typeof Bun !== "undefined";
120
+ }
121
+ /**
122
+ * Returns `true` when the current process is running under Deno.
123
+ */
124
+ function isDeno() {
125
+ return typeof globalThis.Deno !== "undefined";
126
+ }
127
+ /**
128
+ * Returns the name of the runtime executing the current process.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * getRuntimeName() // 'bun' when run with `bun kubb`, 'node' otherwise
133
+ * ```
134
+ */
135
+ function getRuntimeName() {
136
+ if (isBun()) return "bun";
137
+ if (isDeno()) return "deno";
138
+ return "node";
139
+ }
140
+ /**
141
+ * Returns the version of the active runtime, or an empty string when it cannot be read.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * getRuntimeVersion() // '1.3.11' under Bun, '22.22.2' under Node
146
+ * ```
147
+ */
148
+ function getRuntimeVersion() {
149
+ if (isBun()) return process.versions.bun ?? "";
150
+ if (isDeno()) return globalThis.Deno?.version?.deno ?? "";
151
+ return process.versions?.node ?? "";
152
+ }
153
+ //#endregion
84
154
  //#region ../../internals/utils/src/fs.ts
85
155
  /**
156
+ * Reads the file at `path` as a UTF-8 string.
157
+ * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const source = await read('./src/Pet.ts')
162
+ * ```
163
+ */
164
+ async function read(path) {
165
+ if (isBun()) return Bun.file(path).text();
166
+ return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
167
+ }
168
+ /**
86
169
  * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
87
170
  * Skips the write when the trimmed content is empty or identical to what is already on disk.
88
171
  * Creates any missing parent directories automatically.
@@ -99,7 +182,7 @@ async function write(path, data, options = {}) {
99
182
  const trimmed = data.trim();
100
183
  if (trimmed === "") return null;
101
184
  const resolved = (0, node_path.resolve)(path);
102
- if (typeof Bun !== "undefined") {
185
+ if (isBun()) {
103
186
  const file = Bun.file(resolved);
104
187
  if ((await file.exists() ? await file.text() : null) === trimmed) return null;
105
188
  await Bun.write(resolved, trimmed);
@@ -212,6 +295,35 @@ function createAdapter(build) {
212
295
  return (options) => build(options ?? {});
213
296
  }
214
297
  //#endregion
298
+ //#region src/createCache.ts
299
+ /**
300
+ * Defines a custom cache backend. The builder receives user options and returns a
301
+ * {@link Cache}. Reach for this when the filesystem backend doesn't fit, for
302
+ * example to store snapshots in Redis or a database.
303
+ *
304
+ * @example In-memory cache (the built-in implementation)
305
+ * ```ts
306
+ * import { createCache } from '@kubb/core'
307
+ *
308
+ * export const memoryCache = createCache(() => {
309
+ * const store = new Map<string, CachedSnapshot>()
310
+ *
311
+ * return {
312
+ * name: 'memory',
313
+ * async restore({ key }) {
314
+ * return store.get(key) ?? null
315
+ * },
316
+ * async persist({ key, snapshot }) {
317
+ * store.set(key, snapshot)
318
+ * },
319
+ * }
320
+ * })
321
+ * ```
322
+ */
323
+ function createCache(build) {
324
+ return (options) => build(options ?? {});
325
+ }
326
+ //#endregion
215
327
  //#region src/storages/fsStorage.ts
216
328
  /**
217
329
  * Built-in filesystem storage driver.
@@ -263,21 +375,21 @@ const fsStorage = require_memoryStorage.createStorage(() => ({
263
375
  },
264
376
  async getKeys(base) {
265
377
  const resolvedBase = (0, node_path.resolve)(base ?? process.cwd());
266
- async function* walk(dir, prefix) {
267
- let entries;
268
- try {
269
- entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true });
270
- } catch (_error) {
271
- return;
272
- }
273
- for (const entry of entries) {
274
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
275
- if (entry.isDirectory()) yield* walk((0, node_path.join)(dir, entry.name), rel);
276
- else yield rel;
277
- }
378
+ if (isBun()) {
379
+ const bunGlob = new Bun.Glob("**/*");
380
+ return Array.fromAsync(bunGlob.scan({
381
+ cwd: resolvedBase,
382
+ onlyFiles: true,
383
+ dot: true
384
+ }));
278
385
  }
279
386
  const keys = [];
280
- for await (const key of walk(resolvedBase, "")) keys.push(key);
387
+ try {
388
+ for await (const entry of (0, node_fs_promises.glob)("**/*", {
389
+ cwd: resolvedBase,
390
+ withFileTypes: true
391
+ })) if (entry.isFile()) keys.push(toPosixPath((0, node_path.relative)(resolvedBase, (0, node_path.join)(entry.parentPath, entry.name))));
392
+ } catch (_error) {}
281
393
  return keys;
282
394
  },
283
395
  async clear(base) {
@@ -336,6 +448,7 @@ function resolveConfig(userConfig) {
336
448
  ...userConfig.output
337
449
  },
338
450
  storage: userConfig.storage ?? fsStorage(),
451
+ cache: userConfig.cache === false ? void 0 : userConfig.cache,
339
452
  reporters: userConfig.reporters ?? [],
340
453
  plugins: userConfig.plugins ?? []
341
454
  };
@@ -766,6 +879,8 @@ var Telemetry = class Telemetry {
766
879
  command: options.command,
767
880
  kubbVersion: options.kubbVersion,
768
881
  nodeVersion: node_process.default.versions.node.split(".")[0],
882
+ runtime: getRuntimeName(),
883
+ runtimeVersion: getRuntimeVersion().split(".")[0],
769
884
  platform: node_os.default.platform(),
770
885
  ci: isCIEnvironment(),
771
886
  plugins: options.plugins ?? [],
@@ -796,6 +911,14 @@ var Telemetry = class Telemetry {
796
911
  key: "kubb.node_version",
797
912
  value: { stringValue: event.nodeVersion }
798
913
  },
914
+ {
915
+ key: "kubb.runtime",
916
+ value: { stringValue: event.runtime }
917
+ },
918
+ {
919
+ key: "kubb.runtime_version",
920
+ value: { stringValue: event.runtimeVersion }
921
+ },
799
922
  {
800
923
  key: "kubb.platform",
801
924
  value: { stringValue: event.platform }
@@ -1027,6 +1150,132 @@ function defineParser(parser) {
1027
1150
  return parser;
1028
1151
  }
1029
1152
  //#endregion
1153
+ //#region src/Manifest.ts
1154
+ /**
1155
+ * Reads and prunes the local cache manifest. All methods are static, so call them as
1156
+ * `Manifest.read(dir)` and `Manifest.prune(data, ...)`. A damaged manifest reads as empty so the
1157
+ * cache degrades to misses instead of throwing. Writing goes through `write` from `@internals/utils`.
1158
+ */
1159
+ var Manifest = class Manifest {
1160
+ /**
1161
+ * On-disk layout version for the manifest itself. Bumped when the manifest shape changes; a
1162
+ * mismatch makes the whole local cache read as empty.
1163
+ */
1164
+ static version = 1;
1165
+ /**
1166
+ * Reads the manifest at `dir/manifest.json`. A missing, corrupt, or version-mismatched file reads
1167
+ * as an empty manifest.
1168
+ */
1169
+ static async read(dir) {
1170
+ try {
1171
+ const parsed = JSON.parse(await read((0, node_path.join)(dir, "manifest.json")));
1172
+ if (parsed.version !== Manifest.version || typeof parsed.entries !== "object") return Manifest.#empty();
1173
+ return parsed;
1174
+ } catch {
1175
+ return Manifest.#empty();
1176
+ }
1177
+ }
1178
+ /**
1179
+ * Selects the keys to evict so the cache stays within `ttlDays` and `maxEntries`. Returns the
1180
+ * surviving manifest plus the evicted keys (the caller deletes their blobs). Pure, does no IO.
1181
+ */
1182
+ static prune(manifest, { maxEntries, ttlDays, now }) {
1183
+ const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
1184
+ const removed = [];
1185
+ const kept = [];
1186
+ for (const [key, entry] of Object.entries(manifest.entries)) if (now - entry.lastAccess > ttlMs) removed.push(key);
1187
+ else kept.push([key, entry]);
1188
+ if (kept.length > maxEntries) {
1189
+ kept.sort((a, b) => b[1].lastAccess - a[1].lastAccess);
1190
+ for (const [key] of kept.splice(maxEntries)) removed.push(key);
1191
+ }
1192
+ return {
1193
+ manifest: {
1194
+ version: Manifest.version,
1195
+ entries: Object.fromEntries(kept)
1196
+ },
1197
+ removed
1198
+ };
1199
+ }
1200
+ static #empty() {
1201
+ return {
1202
+ version: Manifest.version,
1203
+ entries: {}
1204
+ };
1205
+ }
1206
+ };
1207
+ //#endregion
1208
+ //#region src/caches/fsCache.ts
1209
+ function blobName(relativePath) {
1210
+ return `${(0, node_crypto.createHash)("sha256").update(relativePath).digest("hex")}.blob`;
1211
+ }
1212
+ /**
1213
+ * Local filesystem cache. Stores each build snapshot as content blobs plus an index,
1214
+ * tracked by a manifest under `node_modules/.cache/kubb/` (the Nx and Vitest
1215
+ * convention). Least-recently-used and expired entries are pruned on every persist.
1216
+ *
1217
+ * @example
1218
+ * ```ts
1219
+ * import { fsCache } from '@kubb/core'
1220
+ *
1221
+ * export default defineConfig({
1222
+ * cache: fsCache(),
1223
+ * })
1224
+ * ```
1225
+ */
1226
+ const fsCache = createCache((options = {}) => {
1227
+ const dir = (0, node_path.resolve)(options.dir ?? (0, node_path.join)("node_modules", ".cache", "kubb"));
1228
+ const maxEntries = options.maxEntries ?? 50;
1229
+ const ttlDays = options.ttlDays ?? 7;
1230
+ const blobsDir = (0, node_path.join)(dir, "blobs");
1231
+ const manifestPath = (0, node_path.join)(dir, "manifest.json");
1232
+ return {
1233
+ name: "fs",
1234
+ async restore({ key }) {
1235
+ const manifest = await Manifest.read(dir);
1236
+ const entry = manifest.entries[key];
1237
+ if (!entry) return null;
1238
+ try {
1239
+ const index = JSON.parse(await read((0, node_path.join)(blobsDir, key, "index.json")));
1240
+ const files = {};
1241
+ for (const { path, blob } of index) files[path] = await read((0, node_path.join)(blobsDir, key, blob));
1242
+ entry.lastAccess = Date.now();
1243
+ await write(manifestPath, JSON.stringify(manifest)).catch(() => {});
1244
+ return { files };
1245
+ } catch {
1246
+ return null;
1247
+ }
1248
+ },
1249
+ async persist({ key, snapshot }) {
1250
+ const entryDir = (0, node_path.join)(blobsDir, key);
1251
+ const index = [];
1252
+ for (const [path, source] of Object.entries(snapshot.files)) {
1253
+ const blob = blobName(path);
1254
+ await write((0, node_path.join)(entryDir, blob), source);
1255
+ index.push({
1256
+ path,
1257
+ blob
1258
+ });
1259
+ }
1260
+ await write((0, node_path.join)(entryDir, "index.json"), JSON.stringify(index));
1261
+ const manifest = await Manifest.read(dir);
1262
+ const now = Date.now();
1263
+ manifest.entries[key] = {
1264
+ files: index.map((item) => item.path),
1265
+ createdAt: now,
1266
+ lastAccess: now
1267
+ };
1268
+ const pruned = Manifest.prune(manifest, {
1269
+ maxEntries,
1270
+ ttlDays,
1271
+ now
1272
+ });
1273
+ await Promise.all(pruned.removed.map((removedKey) => clean((0, node_path.join)(blobsDir, removedKey))));
1274
+ await write(manifestPath, JSON.stringify(pruned.manifest));
1275
+ }
1276
+ };
1277
+ });
1278
+ //#endregion
1030
1279
  exports.AsyncEventEmitter = require_memoryStorage.AsyncEventEmitter;
1031
1280
  exports.Diagnostics = require_memoryStorage.Diagnostics;
1032
1281
  exports.KubbDriver = require_memoryStorage.KubbDriver;
@@ -1040,6 +1289,7 @@ Object.defineProperty(exports, "ast", {
1040
1289
  });
1041
1290
  exports.cliReporter = cliReporter;
1042
1291
  exports.createAdapter = createAdapter;
1292
+ exports.createCache = createCache;
1043
1293
  exports.createKubb = createKubb;
1044
1294
  exports.createRenderer = createRenderer;
1045
1295
  exports.createReporter = createReporter;
@@ -1051,6 +1301,7 @@ exports.defineParser = defineParser;
1051
1301
  exports.definePlugin = require_memoryStorage.definePlugin;
1052
1302
  exports.defineResolver = require_memoryStorage.defineResolver;
1053
1303
  exports.fileReporter = fileReporter;
1304
+ exports.fsCache = fsCache;
1054
1305
  exports.fsStorage = fsStorage;
1055
1306
  exports.jsonReporter = jsonReporter;
1056
1307
  exports.logLevel = logLevel;