@kubb/core 5.0.0-beta.40 → 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.
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_memoryStorage = require("./memoryStorage-Dkxnid2K.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
  };
@@ -531,11 +644,10 @@ const logLevel = {
531
644
  verbose: 4
532
645
  };
533
646
  /**
534
- * Defines a typed logger. Use the second type parameter to declare a return
535
- * value from `install`, which is handy when the logger exposes a sink factory
536
- * or cleanup callback to the caller.
647
+ * Defines a typed logger. The `install` method subscribes to lifecycle events
648
+ * on the shared emitter and forwards them to the logger's destination.
537
649
  *
538
- * @example Basic logger
650
+ * @example
539
651
  * ```ts
540
652
  * import { defineLogger } from '@kubb/core'
541
653
  *
@@ -547,20 +659,6 @@ const logLevel = {
547
659
  * },
548
660
  * })
549
661
  * ```
550
- *
551
- * @example Logger that returns a hook sink factory
552
- * ```ts
553
- * import { defineLogger, type LoggerOptions } from '@kubb/core'
554
- * import type { HookSinkFactory } from './sinks'
555
- *
556
- * export const myLogger = defineLogger<LoggerOptions, HookSinkFactory>({
557
- * name: 'my-logger',
558
- * install(context) {
559
- * // … register event handlers …
560
- * return () => ({ onStdout: console.log })
561
- * },
562
- * })
563
- * ```
564
662
  */
565
663
  function defineLogger(logger) {
566
664
  return logger;
@@ -781,6 +879,8 @@ var Telemetry = class Telemetry {
781
879
  command: options.command,
782
880
  kubbVersion: options.kubbVersion,
783
881
  nodeVersion: node_process.default.versions.node.split(".")[0],
882
+ runtime: getRuntimeName(),
883
+ runtimeVersion: getRuntimeVersion().split(".")[0],
784
884
  platform: node_os.default.platform(),
785
885
  ci: isCIEnvironment(),
786
886
  plugins: options.plugins ?? [],
@@ -811,6 +911,14 @@ var Telemetry = class Telemetry {
811
911
  key: "kubb.node_version",
812
912
  value: { stringValue: event.nodeVersion }
813
913
  },
914
+ {
915
+ key: "kubb.runtime",
916
+ value: { stringValue: event.runtime }
917
+ },
918
+ {
919
+ key: "kubb.runtime_version",
920
+ value: { stringValue: event.runtimeVersion }
921
+ },
814
922
  {
815
923
  key: "kubb.platform",
816
924
  value: { stringValue: event.platform }
@@ -1042,6 +1150,132 @@ function defineParser(parser) {
1042
1150
  return parser;
1043
1151
  }
1044
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
1045
1279
  exports.AsyncEventEmitter = require_memoryStorage.AsyncEventEmitter;
1046
1280
  exports.Diagnostics = require_memoryStorage.Diagnostics;
1047
1281
  exports.KubbDriver = require_memoryStorage.KubbDriver;
@@ -1055,6 +1289,7 @@ Object.defineProperty(exports, "ast", {
1055
1289
  });
1056
1290
  exports.cliReporter = cliReporter;
1057
1291
  exports.createAdapter = createAdapter;
1292
+ exports.createCache = createCache;
1058
1293
  exports.createKubb = createKubb;
1059
1294
  exports.createRenderer = createRenderer;
1060
1295
  exports.createReporter = createReporter;
@@ -1066,6 +1301,7 @@ exports.defineParser = defineParser;
1066
1301
  exports.definePlugin = require_memoryStorage.definePlugin;
1067
1302
  exports.defineResolver = require_memoryStorage.defineResolver;
1068
1303
  exports.fileReporter = fileReporter;
1304
+ exports.fsCache = fsCache;
1069
1305
  exports.fsStorage = fsStorage;
1070
1306
  exports.jsonReporter = jsonReporter;
1071
1307
  exports.logLevel = logLevel;