@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/README.md +13 -13
- package/dist/{diagnostics-DhfW8YpT.d.ts → diagnostics-DZGgDzSv.d.ts} +183 -33
- package/dist/index.cjs +270 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.js +271 -37
- package/dist/index.js.map +1 -1
- package/dist/{memoryStorage-DTv1Kub1.js → memoryStorage-BOnaknb7.js} +170 -23
- package/dist/memoryStorage-BOnaknb7.js.map +1 -0
- package/dist/{memoryStorage-Dkxnid2K.cjs → memoryStorage-Dldu8sRT.cjs} +169 -22
- package/dist/memoryStorage-Dldu8sRT.cjs.map +1 -0
- package/dist/mocks.cjs +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/mocks.js +1 -1
- package/package.json +4 -4
- package/src/Fingerprint.ts +98 -0
- package/src/KubbDriver.ts +102 -33
- package/src/Manifest.ts +85 -0
- package/src/Telemetry.ts +13 -1
- package/src/caches/fsCache.ts +103 -0
- package/src/createCache.ts +74 -0
- package/src/createKubb.ts +57 -4
- package/src/defineLogger.ts +10 -28
- package/src/defineParser.ts +1 -1
- package/src/index.ts +2 -0
- package/src/storages/fsStorage.ts +15 -23
- package/src/types.ts +3 -0
- package/dist/memoryStorage-DTv1Kub1.js.map +0 -1
- package/dist/memoryStorage-Dkxnid2K.cjs.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import "./chunk-C0LytTxp.js";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import { styleText } from "node:util";
|
|
4
|
-
import
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import path, { basename, extname, join, relative, resolve } from "node:path";
|
|
5
7
|
import { collectUsedSchemaNames, createFile, createStreamInput, extractStringsFromNodes, isOperationNode, isSchemaNode, transform } from "@kubb/ast";
|
|
6
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
9
|
//#region ../../internals/utils/src/errors.ts
|
|
@@ -635,7 +637,7 @@ var URLPath = class {
|
|
|
635
637
|
};
|
|
636
638
|
//#endregion
|
|
637
639
|
//#region package.json
|
|
638
|
-
var version = "5.0.0-beta.
|
|
640
|
+
var version = "5.0.0-beta.42";
|
|
639
641
|
//#endregion
|
|
640
642
|
//#region src/constants.ts
|
|
641
643
|
/**
|
|
@@ -1204,6 +1206,89 @@ function createStorage(build) {
|
|
|
1204
1206
|
return (options) => build(options ?? {});
|
|
1205
1207
|
}
|
|
1206
1208
|
//#endregion
|
|
1209
|
+
//#region src/Fingerprint.ts
|
|
1210
|
+
/**
|
|
1211
|
+
* Computes the cache key for an incremental build. All methods are static, so call them as
|
|
1212
|
+
* `Fingerprint.compute(...)` and `Fingerprint.stringify(...)`. The key holds no absolute
|
|
1213
|
+
* paths or modification times, so it never depends on where the project lives on disk.
|
|
1214
|
+
*/
|
|
1215
|
+
var Fingerprint = class Fingerprint {
|
|
1216
|
+
/**
|
|
1217
|
+
* Bumped when the snapshot format or fingerprint inputs change in an incompatible way, so stale
|
|
1218
|
+
* cache entries from older Kubb builds are never reused.
|
|
1219
|
+
*/
|
|
1220
|
+
static version = 1;
|
|
1221
|
+
/**
|
|
1222
|
+
* Deterministically serializes a value to JSON: object keys are sorted recursively and
|
|
1223
|
+
* `undefined` values and functions are dropped. Two structurally equal configs produce the same
|
|
1224
|
+
* string regardless of key order, which keeps the fingerprint stable across machines.
|
|
1225
|
+
*/
|
|
1226
|
+
static stringify(value) {
|
|
1227
|
+
return JSON.stringify(Fingerprint.#normalize(value));
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Computes a cache key from everything that affects the generated output: the spec content, the
|
|
1231
|
+
* output-shaping config, each plugin's name and options, the middleware names, the running
|
|
1232
|
+
* `@kubb/core` version, and the cache format version. Returns `null` when the input can't be
|
|
1233
|
+
* fingerprinted (remote URL or no adapter source), which disables caching for that build.
|
|
1234
|
+
*/
|
|
1235
|
+
static async compute({ config, adapterSource, version }) {
|
|
1236
|
+
if (!adapterSource) return null;
|
|
1237
|
+
const spec = await Fingerprint.#readSpec(adapterSource, config.root);
|
|
1238
|
+
if (spec === null) return null;
|
|
1239
|
+
const input = {
|
|
1240
|
+
cacheVersion: Fingerprint.version,
|
|
1241
|
+
version,
|
|
1242
|
+
spec,
|
|
1243
|
+
name: config.name,
|
|
1244
|
+
output: config.output,
|
|
1245
|
+
adapter: config.adapter?.name,
|
|
1246
|
+
parsers: config.parsers.map((parser) => parser.name),
|
|
1247
|
+
plugins: config.plugins.map((plugin) => ({
|
|
1248
|
+
name: plugin.name,
|
|
1249
|
+
options: plugin.options
|
|
1250
|
+
})),
|
|
1251
|
+
middleware: (config.middleware ?? []).map((middleware) => middleware.name)
|
|
1252
|
+
};
|
|
1253
|
+
return createHash("sha256").update(Fingerprint.stringify(input)).digest("hex");
|
|
1254
|
+
}
|
|
1255
|
+
static #normalize(value) {
|
|
1256
|
+
if (value === null || typeof value !== "object") return typeof value === "function" ? void 0 : value;
|
|
1257
|
+
if (Array.isArray(value)) return value.map((item) => Fingerprint.#normalize(item));
|
|
1258
|
+
const source = value;
|
|
1259
|
+
const result = {};
|
|
1260
|
+
for (const key of Object.keys(source).sort()) {
|
|
1261
|
+
const normalized = Fingerprint.#normalize(source[key]);
|
|
1262
|
+
if (normalized !== void 0) result[key] = normalized;
|
|
1263
|
+
}
|
|
1264
|
+
return result;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Reads the spec content that feeds the fingerprint. Returns `null` for a remote URL source
|
|
1268
|
+
* (hashing remote content would mean fetching it on every run) or when a file can't be read, so a
|
|
1269
|
+
* missing or virtual spec disables caching instead of failing the build.
|
|
1270
|
+
*/
|
|
1271
|
+
static async #readSpec(source, root) {
|
|
1272
|
+
if (source.type === "data") return {
|
|
1273
|
+
kind: "data",
|
|
1274
|
+
data: typeof source.data === "string" ? source.data : Fingerprint.stringify(source.data)
|
|
1275
|
+
};
|
|
1276
|
+
const paths = source.type === "paths" ? source.paths : [source.path];
|
|
1277
|
+
if (paths.some((path) => new URLPath(path).isURL)) return null;
|
|
1278
|
+
try {
|
|
1279
|
+
return {
|
|
1280
|
+
kind: "path",
|
|
1281
|
+
contents: await Promise.all(paths.map(async (path) => ({
|
|
1282
|
+
path: relative(root, path),
|
|
1283
|
+
content: await readFile(path, "utf8")
|
|
1284
|
+
})))
|
|
1285
|
+
};
|
|
1286
|
+
} catch {
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
//#endregion
|
|
1207
1292
|
//#region src/definePlugin.ts
|
|
1208
1293
|
/**
|
|
1209
1294
|
* Wraps a plugin factory and returns a function that accepts user options and
|
|
@@ -2098,9 +2183,10 @@ var KubbDriver = class KubbDriver {
|
|
|
2098
2183
|
}
|
|
2099
2184
|
async setup() {
|
|
2100
2185
|
const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
|
|
2186
|
+
const dependenciesByName = new Map(normalized.map((plugin) => [plugin.name, new Set(plugin.dependencies ?? [])]));
|
|
2101
2187
|
normalized.sort((a, b) => {
|
|
2102
|
-
if (b.
|
|
2103
|
-
if (a.
|
|
2188
|
+
if (dependenciesByName.get(b.name)?.has(a.name)) return -1;
|
|
2189
|
+
if (dependenciesByName.get(a.name)?.has(b.name)) return 1;
|
|
2104
2190
|
return enforceOrder(a.enforce) - enforceOrder(b.enforce);
|
|
2105
2191
|
});
|
|
2106
2192
|
for (const plugin of normalized) {
|
|
@@ -2325,8 +2411,10 @@ var KubbDriver = class KubbDriver {
|
|
|
2325
2411
|
await hooks.emit("kubb:files:processing:start", { files });
|
|
2326
2412
|
});
|
|
2327
2413
|
const updateBuffer = [];
|
|
2414
|
+
const snapshotSources = /* @__PURE__ */ new Map();
|
|
2328
2415
|
processor.hooks.on("update", (item) => {
|
|
2329
2416
|
updateBuffer.push(item);
|
|
2417
|
+
if (item.source !== void 0) snapshotSources.set(item.file.path, item.source);
|
|
2330
2418
|
});
|
|
2331
2419
|
processor.hooks.on("end", async (files) => {
|
|
2332
2420
|
await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
|
|
@@ -2342,6 +2430,19 @@ var KubbDriver = class KubbDriver {
|
|
|
2342
2430
|
this.fileManager.hooks.on("upsert", onFileUpsert);
|
|
2343
2431
|
return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
|
|
2344
2432
|
try {
|
|
2433
|
+
const cache = config.cache;
|
|
2434
|
+
const outputRoot = resolve(config.root, config.output.path);
|
|
2435
|
+
const cacheKey = cache ? await Fingerprint.compute({
|
|
2436
|
+
config,
|
|
2437
|
+
adapterSource: this.#adapterSource,
|
|
2438
|
+
version
|
|
2439
|
+
}) : null;
|
|
2440
|
+
if (cache && cacheKey && await this.#restoreSnapshot({
|
|
2441
|
+
cache,
|
|
2442
|
+
cacheKey,
|
|
2443
|
+
outputRoot,
|
|
2444
|
+
storage
|
|
2445
|
+
})) return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
2345
2446
|
await this.#parseInput();
|
|
2346
2447
|
await this.emitSetupHooks();
|
|
2347
2448
|
if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
|
|
@@ -2400,7 +2501,13 @@ var KubbDriver = class KubbDriver {
|
|
|
2400
2501
|
await hooks.emit("kubb:build:end", {
|
|
2401
2502
|
files: this.fileManager.files,
|
|
2402
2503
|
config,
|
|
2403
|
-
outputDir:
|
|
2504
|
+
outputDir: outputRoot
|
|
2505
|
+
});
|
|
2506
|
+
if (cache && cacheKey && !Diagnostics.hasError(diagnostics)) await this.#persistSnapshot({
|
|
2507
|
+
cache,
|
|
2508
|
+
cacheKey,
|
|
2509
|
+
outputRoot,
|
|
2510
|
+
sources: snapshotSources
|
|
2404
2511
|
});
|
|
2405
2512
|
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
2406
2513
|
} catch (caughtError) {
|
|
@@ -2411,6 +2518,40 @@ var KubbDriver = class KubbDriver {
|
|
|
2411
2518
|
}
|
|
2412
2519
|
});
|
|
2413
2520
|
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Writes a restored snapshot straight to storage and emits `kubb:build:end`. Returns `true` on a
|
|
2523
|
+
* hit (the build is done), `false` on a miss so the caller falls through to a full build.
|
|
2524
|
+
*/
|
|
2525
|
+
async #restoreSnapshot({ cache, cacheKey, outputRoot, storage }) {
|
|
2526
|
+
const snapshot = await cache.restore({ key: cacheKey });
|
|
2527
|
+
if (!snapshot) return false;
|
|
2528
|
+
for (const [relativePath, source] of Object.entries(snapshot.files)) {
|
|
2529
|
+
const absolutePath = join(outputRoot, relativePath);
|
|
2530
|
+
this.fileManager.upsert(createFile({
|
|
2531
|
+
path: absolutePath,
|
|
2532
|
+
baseName: basename(relativePath)
|
|
2533
|
+
}));
|
|
2534
|
+
await storage.setItem(absolutePath, source);
|
|
2535
|
+
}
|
|
2536
|
+
await this.hooks.emit("kubb:build:end", {
|
|
2537
|
+
files: this.fileManager.files,
|
|
2538
|
+
config: this.config,
|
|
2539
|
+
outputDir: outputRoot
|
|
2540
|
+
});
|
|
2541
|
+
return true;
|
|
2542
|
+
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Stores this run's rendered output, keyed by the input fingerprint, so the next unchanged build
|
|
2545
|
+
* restores it instead of regenerating. `sources` is keyed by absolute path and relativized here.
|
|
2546
|
+
*/
|
|
2547
|
+
async #persistSnapshot({ cache, cacheKey, outputRoot, sources }) {
|
|
2548
|
+
const files = {};
|
|
2549
|
+
for (const [absolutePath, source] of sources) files[relative(outputRoot, absolutePath)] = source;
|
|
2550
|
+
await cache.persist({
|
|
2551
|
+
key: cacheKey,
|
|
2552
|
+
snapshot: { files }
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2414
2555
|
#filesPayload() {
|
|
2415
2556
|
const driver = this;
|
|
2416
2557
|
return {
|
|
@@ -2437,6 +2578,11 @@ var KubbDriver = class KubbDriver {
|
|
|
2437
2578
|
* A failing plugin contributes an error diagnostic so the rest of the build continues.
|
|
2438
2579
|
* Every plugin also contributes a `timing` diagnostic.
|
|
2439
2580
|
*
|
|
2581
|
+
* Plugins run sequentially so `kubb:plugin:end` fires as each plugin completes, instead
|
|
2582
|
+
* of all at once after every plugin has marched through the parallel batches together.
|
|
2583
|
+
* That ordering is what drives the CLI's `Plugins N/M` counter; without it the bar would
|
|
2584
|
+
* sit at the initial value until the very end of the run.
|
|
2585
|
+
*
|
|
2440
2586
|
* When `entries` is empty or `this.inputNode` is `null`, every entry still gets a
|
|
2441
2587
|
* `kubb:plugin:end` so middleware listeners (the barrel writer and friends) complete.
|
|
2442
2588
|
*/
|
|
@@ -2481,15 +2627,16 @@ var KubbDriver = class KubbDriver {
|
|
|
2481
2627
|
});
|
|
2482
2628
|
const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
|
|
2483
2629
|
const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
|
|
2630
|
+
const emitsOperationsHook = this.hooks.listenerCount("kubb:generate:operations") > 0;
|
|
2631
|
+
const schemasBuffer = await Array.fromAsync(schemas);
|
|
2632
|
+
const operationsBuffer = await Array.fromAsync(operations);
|
|
2484
2633
|
const pruningStates = states.filter(({ plugin }) => {
|
|
2485
2634
|
const { include } = plugin.options;
|
|
2486
2635
|
return (include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
|
|
2487
2636
|
});
|
|
2488
2637
|
if (pruningStates.length > 0) {
|
|
2489
|
-
const allSchemas = [];
|
|
2490
|
-
for await (const schema of schemas) allSchemas.push(schema);
|
|
2491
2638
|
const includedOpsByState = new Map(pruningStates.map((state) => [state, []]));
|
|
2492
|
-
for
|
|
2639
|
+
for (const operation of operationsBuffer) for (const state of pruningStates) {
|
|
2493
2640
|
const { exclude, include, override } = state.plugin.options;
|
|
2494
2641
|
if (state.generatorContext.resolver.resolveOptions(operation, {
|
|
2495
2642
|
options: state.plugin.options,
|
|
@@ -2499,7 +2646,7 @@ var KubbDriver = class KubbDriver {
|
|
|
2499
2646
|
}) !== null) includedOpsByState.get(state)?.push(operation);
|
|
2500
2647
|
}
|
|
2501
2648
|
for (const state of pruningStates) {
|
|
2502
|
-
state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [],
|
|
2649
|
+
state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], schemasBuffer);
|
|
2503
2650
|
includedOpsByState.delete(state);
|
|
2504
2651
|
}
|
|
2505
2652
|
}
|
|
@@ -2561,20 +2708,20 @@ var KubbDriver = class KubbDriver {
|
|
|
2561
2708
|
checkAllowedNames: false,
|
|
2562
2709
|
emit: emitsOperationHook ? (node, ctx) => this.hooks.emit("kubb:generate:operation", node, ctx) : null
|
|
2563
2710
|
};
|
|
2564
|
-
const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((state) => state.generators.some((gen) => !!gen.operations));
|
|
2565
|
-
const collectedOperations = needsCollectedOperations ? [] : void 0;
|
|
2566
|
-
await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((node) => states.map((state) => dispatchNode(state, node, schemaDispatch)))), {
|
|
2567
|
-
concurrency: 8,
|
|
2568
|
-
flush: flushPending
|
|
2569
|
-
});
|
|
2570
|
-
await forBatches(operations, (nodes) => {
|
|
2571
|
-
if (needsCollectedOperations) collectedOperations?.push(...nodes);
|
|
2572
|
-
return Promise.all(nodes.flatMap((node) => states.map((state) => dispatchNode(state, node, operationDispatch))));
|
|
2573
|
-
}, {
|
|
2574
|
-
concurrency: 8,
|
|
2575
|
-
flush: flushPending
|
|
2576
|
-
});
|
|
2577
2711
|
for (const state of states) {
|
|
2712
|
+
const needsCollectedOperations = emitsOperationsHook || state.generators.some((gen) => !!gen.operations);
|
|
2713
|
+
const collectedOperations = needsCollectedOperations ? [] : void 0;
|
|
2714
|
+
await forBatches(schemasBuffer, (nodes) => Promise.all(nodes.map((node) => dispatchNode(state, node, schemaDispatch))), {
|
|
2715
|
+
concurrency: 8,
|
|
2716
|
+
flush: flushPending
|
|
2717
|
+
});
|
|
2718
|
+
await forBatches(operationsBuffer, (nodes) => {
|
|
2719
|
+
if (needsCollectedOperations) collectedOperations?.push(...nodes);
|
|
2720
|
+
return Promise.all(nodes.map((node) => dispatchNode(state, node, operationDispatch)));
|
|
2721
|
+
}, {
|
|
2722
|
+
concurrency: 8,
|
|
2723
|
+
flush: flushPending
|
|
2724
|
+
});
|
|
2578
2725
|
if (!state.failed && needsCollectedOperations) try {
|
|
2579
2726
|
const { plugin, generatorContext, generators } = state;
|
|
2580
2727
|
const ctx = {
|
|
@@ -2849,4 +2996,4 @@ const memoryStorage = createStorage(() => {
|
|
|
2849
2996
|
//#endregion
|
|
2850
2997
|
export { FileManager as a, createStorage as c, URLPath as d, formatMs as f, BuildError as g, AsyncEventEmitter as h, FileProcessor as i, Diagnostics as l, camelCase as m, KubbDriver as n, defineResolver as o, getElapsedMs as p, _usingCtx as r, definePlugin as s, memoryStorage as t, OTLP_ENDPOINT as u };
|
|
2851
2998
|
|
|
2852
|
-
//# sourceMappingURL=memoryStorage-
|
|
2999
|
+
//# sourceMappingURL=memoryStorage-BOnaknb7.js.map
|