@kubb/core 5.0.0-beta.54 → 5.0.0-beta.56
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/{diagnostics-D_LOtOCv.d.ts → diagnostics-Bf2bC8lV.d.ts} +10 -159
- package/dist/index.cjs +155 -459
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +57 -94
- package/dist/index.js +157 -459
- package/dist/index.js.map +1 -1
- package/dist/{memoryStorage-Bdv42rxp.js → memoryStorage-B0W-w994.js} +14 -39
- package/dist/memoryStorage-B0W-w994.js.map +1 -0
- package/dist/{memoryStorage-CIEzDI6b.cjs → memoryStorage-skOz0dXZ.cjs} +14 -39
- package/dist/memoryStorage-skOz0dXZ.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/KubbDriver.ts +7 -71
- package/src/createAdapter.ts +3 -3
- package/src/createKubb.ts +0 -3
- package/src/defineResolver.ts +4 -4
- package/src/index.ts +1 -3
- package/src/storages/fsStorage.ts +7 -7
- package/src/types.ts +1 -34
- package/dist/memoryStorage-Bdv42rxp.js.map +0 -1
- package/dist/memoryStorage-CIEzDI6b.cjs.map +0 -1
- package/src/Fingerprint.ts +0 -97
- package/src/caches/fsCache.ts +0 -185
- package/src/createCache.ts +0 -74
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_memoryStorage = require("./memoryStorage-
|
|
2
|
+
const require_memoryStorage = require("./memoryStorage-skOz0dXZ.cjs");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
let node_util = require("node:util");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -98,37 +98,75 @@ function randomCliColor(text) {
|
|
|
98
98
|
//#endregion
|
|
99
99
|
//#region ../../internals/utils/src/runtime.ts
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
101
|
+
* Detects the JavaScript runtime executing the current process and exposes its name and version.
|
|
102
102
|
*
|
|
103
|
-
*
|
|
104
|
-
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
105
|
-
* otherwise look like Node.
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* ```ts
|
|
109
|
-
* if (isBun()) {
|
|
110
|
-
* await Bun.write(path, data)
|
|
111
|
-
* }
|
|
112
|
-
* ```
|
|
103
|
+
* Prefer the shared {@link runtime} instance over constructing your own.
|
|
113
104
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
var Runtime = class {
|
|
106
|
+
/**
|
|
107
|
+
* `true` when the current process is running under Bun.
|
|
108
|
+
*
|
|
109
|
+
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
110
|
+
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
111
|
+
* otherwise look like Node.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* if (runtime.isBun) {
|
|
116
|
+
* await Bun.write(path, data)
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
get isBun() {
|
|
121
|
+
return typeof Bun !== "undefined";
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `true` when the current process is running under Deno.
|
|
125
|
+
*/
|
|
126
|
+
get isDeno() {
|
|
127
|
+
return typeof globalThis.Deno !== "undefined";
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* `true` when the current process is running under Node.
|
|
131
|
+
*
|
|
132
|
+
* Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
|
|
133
|
+
*/
|
|
134
|
+
get isNode() {
|
|
135
|
+
return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Name of the runtime executing the current process.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
get name() {
|
|
146
|
+
if (this.isBun) return "bun";
|
|
147
|
+
if (this.isDeno) return "deno";
|
|
148
|
+
return "node";
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Version of the active runtime, or an empty string when it cannot be read.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* runtime.version // '1.3.11' under Bun, '22.22.2' under Node
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
get version() {
|
|
159
|
+
if (this.isBun) return process.versions.bun ?? "";
|
|
160
|
+
if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
|
|
161
|
+
return process.versions?.node ?? "";
|
|
162
|
+
}
|
|
163
|
+
};
|
|
119
164
|
/**
|
|
120
|
-
*
|
|
121
|
-
* Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```ts
|
|
125
|
-
* const source = await read('./src/Pet.ts')
|
|
126
|
-
* ```
|
|
165
|
+
* Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
|
|
127
166
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
167
|
+
const runtime = new Runtime();
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region ../../internals/utils/src/fs.ts
|
|
132
170
|
/**
|
|
133
171
|
* Writes `data` to `path`, trimming leading/trailing whitespace before saving.
|
|
134
172
|
* Skips the write when the trimmed content is empty or identical to what is already on disk.
|
|
@@ -146,7 +184,7 @@ async function write(path, data, options = {}) {
|
|
|
146
184
|
const trimmed = data.trim();
|
|
147
185
|
if (trimmed === "") return null;
|
|
148
186
|
const resolved = (0, node_path.resolve)(path);
|
|
149
|
-
if (isBun
|
|
187
|
+
if (runtime.isBun) {
|
|
150
188
|
const file = Bun.file(resolved);
|
|
151
189
|
if ((await file.exists() ? await file.text() : null) === trimmed) return null;
|
|
152
190
|
await Bun.write(resolved, trimmed);
|
|
@@ -178,8 +216,6 @@ async function clean(path) {
|
|
|
178
216
|
force: true
|
|
179
217
|
});
|
|
180
218
|
}
|
|
181
|
-
//#endregion
|
|
182
|
-
//#region ../../internals/utils/src/path.ts
|
|
183
219
|
/**
|
|
184
220
|
* Converts a filesystem path to use POSIX (`/`) separators.
|
|
185
221
|
*
|
|
@@ -197,6 +233,29 @@ async function clean(path) {
|
|
|
197
233
|
function toPosixPath(filePath) {
|
|
198
234
|
return filePath.replaceAll("\\", "/");
|
|
199
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
238
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
239
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
240
|
+
*
|
|
241
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
242
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
243
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
244
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
245
|
+
*
|
|
246
|
+
* @example Nested path from a dotted name
|
|
247
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
248
|
+
*
|
|
249
|
+
* @example PascalCase the final segment
|
|
250
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
251
|
+
*
|
|
252
|
+
* @example Suffix applied to the final segment only
|
|
253
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
254
|
+
*/
|
|
255
|
+
function toFilePath(name, caseLast = require_memoryStorage.camelCase) {
|
|
256
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
257
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : require_memoryStorage.camelCase(part)).filter(Boolean).join("/");
|
|
258
|
+
}
|
|
200
259
|
//#endregion
|
|
201
260
|
//#region ../../internals/utils/src/promise.ts
|
|
202
261
|
function* chunks(arr, size) {
|
|
@@ -427,99 +486,80 @@ function isIdentifier(name) {
|
|
|
427
486
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
428
487
|
}
|
|
429
488
|
//#endregion
|
|
430
|
-
//#region ../../internals/utils/src/
|
|
489
|
+
//#region ../../internals/utils/src/url.ts
|
|
490
|
+
function transformParam(raw, casing) {
|
|
491
|
+
const param = isValidVarName(raw) ? raw : require_memoryStorage.camelCase(raw);
|
|
492
|
+
return casing === "camelcase" ? require_memoryStorage.camelCase(param) : param;
|
|
493
|
+
}
|
|
494
|
+
function toParamsObject(path, { replacer, casing } = {}) {
|
|
495
|
+
const params = {};
|
|
496
|
+
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
497
|
+
const param = transformParam(match[1], casing);
|
|
498
|
+
const key = replacer ? replacer(param) : param;
|
|
499
|
+
params[key] = key;
|
|
500
|
+
}
|
|
501
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
502
|
+
}
|
|
431
503
|
/**
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* @example
|
|
435
|
-
* const p = new URLPath('/pet/{petId}')
|
|
436
|
-
* p.URL // '/pet/:petId'
|
|
437
|
-
* p.template // '`/pet/${petId}`'
|
|
504
|
+
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
438
505
|
*/
|
|
439
|
-
var
|
|
506
|
+
var Url = class Url {
|
|
440
507
|
/**
|
|
441
|
-
*
|
|
442
|
-
*/
|
|
443
|
-
path;
|
|
444
|
-
#options;
|
|
445
|
-
constructor(path, options = {}) {
|
|
446
|
-
this.path = path;
|
|
447
|
-
this.#options = options;
|
|
448
|
-
}
|
|
449
|
-
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
508
|
+
* Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
|
|
450
509
|
*
|
|
451
510
|
* @example
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* ```
|
|
511
|
+
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
512
|
+
* Url.canParse('/pet/{petId}') // false
|
|
455
513
|
*/
|
|
456
|
-
|
|
457
|
-
return
|
|
514
|
+
static canParse(url, base) {
|
|
515
|
+
return URL.canParse(url, base);
|
|
458
516
|
}
|
|
459
|
-
/**
|
|
517
|
+
/**
|
|
518
|
+
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
460
519
|
*
|
|
461
520
|
* @example
|
|
462
|
-
*
|
|
463
|
-
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
464
|
-
* new URLPath('/pet/{petId}').isURL // false
|
|
465
|
-
* ```
|
|
521
|
+
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
466
522
|
*/
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
return !!new URL(this.path).href;
|
|
470
|
-
} catch {
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
523
|
+
static toPath(path) {
|
|
524
|
+
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
473
525
|
}
|
|
474
526
|
/**
|
|
475
|
-
* Converts
|
|
527
|
+
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
528
|
+
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
529
|
+
* and `casing` controls parameter identifier casing.
|
|
476
530
|
*
|
|
477
531
|
* @example
|
|
478
|
-
*
|
|
479
|
-
* new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
|
|
480
|
-
*/
|
|
481
|
-
get template() {
|
|
482
|
-
return this.toTemplateString();
|
|
483
|
-
}
|
|
484
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
532
|
+
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
485
533
|
*
|
|
486
534
|
* @example
|
|
487
|
-
*
|
|
488
|
-
* new URLPath('/pet/{petId}').object
|
|
489
|
-
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
490
|
-
* ```
|
|
535
|
+
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
491
536
|
*/
|
|
492
|
-
|
|
493
|
-
|
|
537
|
+
static toTemplateString(path, { prefix, replacer, casing } = {}) {
|
|
538
|
+
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
539
|
+
if (i % 2 === 0) return part;
|
|
540
|
+
const param = transformParam(part, casing);
|
|
541
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
542
|
+
}).join("");
|
|
543
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
494
544
|
}
|
|
495
|
-
/**
|
|
545
|
+
/**
|
|
546
|
+
* Returns the path and its extracted params as a structured `URLObject`, or as a stringified
|
|
547
|
+
* expression when `stringify` is set.
|
|
496
548
|
*
|
|
497
549
|
* @example
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* new URLPath('/pet').params // undefined
|
|
501
|
-
* ```
|
|
502
|
-
*/
|
|
503
|
-
get params() {
|
|
504
|
-
return this.getParams();
|
|
505
|
-
}
|
|
506
|
-
#transformParam(raw) {
|
|
507
|
-
const param = isValidVarName(raw) ? raw : require_memoryStorage.camelCase(raw);
|
|
508
|
-
return this.#options.casing === "camelcase" ? require_memoryStorage.camelCase(param) : param;
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
550
|
+
* Url.toObject('/pet/{petId}')
|
|
551
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
512
552
|
*/
|
|
513
|
-
|
|
514
|
-
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
515
|
-
const raw = match[1];
|
|
516
|
-
fn(raw, this.#transformParam(raw));
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
toObject({ type = "path", replacer, stringify } = {}) {
|
|
553
|
+
static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
|
|
520
554
|
const object = {
|
|
521
|
-
url: type === "path" ?
|
|
522
|
-
|
|
555
|
+
url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
|
|
556
|
+
replacer,
|
|
557
|
+
casing
|
|
558
|
+
}),
|
|
559
|
+
params: toParamsObject(path, {
|
|
560
|
+
replacer,
|
|
561
|
+
casing
|
|
562
|
+
})
|
|
523
563
|
};
|
|
524
564
|
if (stringify) {
|
|
525
565
|
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
@@ -528,50 +568,6 @@ var URLPath = class {
|
|
|
528
568
|
}
|
|
529
569
|
return object;
|
|
530
570
|
}
|
|
531
|
-
/**
|
|
532
|
-
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
533
|
-
* An optional `replacer` can transform each extracted parameter name before interpolation.
|
|
534
|
-
*
|
|
535
|
-
* @example
|
|
536
|
-
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
537
|
-
*/
|
|
538
|
-
toTemplateString({ prefix, replacer } = {}) {
|
|
539
|
-
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
540
|
-
if (i % 2 === 0) return part;
|
|
541
|
-
const param = this.#transformParam(part);
|
|
542
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
543
|
-
}).join("");
|
|
544
|
-
return `\`${prefix ?? ""}${result}\``;
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
548
|
-
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
549
|
-
* Returns `undefined` when no path parameters are found.
|
|
550
|
-
*
|
|
551
|
-
* @example
|
|
552
|
-
* ```ts
|
|
553
|
-
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
554
|
-
* // { petId: 'petId', tagId: 'tagId' }
|
|
555
|
-
* ```
|
|
556
|
-
*/
|
|
557
|
-
getParams(replacer) {
|
|
558
|
-
const params = {};
|
|
559
|
-
this.#eachParam((_raw, param) => {
|
|
560
|
-
const key = replacer ? replacer(param) : param;
|
|
561
|
-
params[key] = key;
|
|
562
|
-
});
|
|
563
|
-
return Object.keys(params).length > 0 ? params : void 0;
|
|
564
|
-
}
|
|
565
|
-
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
566
|
-
*
|
|
567
|
-
* @example
|
|
568
|
-
* ```ts
|
|
569
|
-
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
570
|
-
* ```
|
|
571
|
-
*/
|
|
572
|
-
toURLPath() {
|
|
573
|
-
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
574
|
-
}
|
|
575
571
|
};
|
|
576
572
|
//#endregion
|
|
577
573
|
//#region src/createAdapter.ts
|
|
@@ -609,43 +605,11 @@ function createAdapter(build) {
|
|
|
609
605
|
return (options) => build(options ?? {});
|
|
610
606
|
}
|
|
611
607
|
//#endregion
|
|
612
|
-
//#region src/createCache.ts
|
|
613
|
-
/**
|
|
614
|
-
* Defines a custom cache backend. The builder receives user options and returns a
|
|
615
|
-
* {@link Cache}. Reach for this when the filesystem backend doesn't fit, for
|
|
616
|
-
* example to store snapshots in Redis or a database.
|
|
617
|
-
*
|
|
618
|
-
* @example In-memory cache (the built-in implementation)
|
|
619
|
-
* ```ts
|
|
620
|
-
* import { createCache } from '@kubb/core'
|
|
621
|
-
*
|
|
622
|
-
* export const memoryCache = createCache(() => {
|
|
623
|
-
* const store = new Map<string, CachedSnapshot>()
|
|
624
|
-
*
|
|
625
|
-
* return {
|
|
626
|
-
* name: 'memory',
|
|
627
|
-
* async restore({ key }) {
|
|
628
|
-
* return store.get(key) ?? null
|
|
629
|
-
* },
|
|
630
|
-
* async persist({ key, snapshot }) {
|
|
631
|
-
* store.set(key, snapshot)
|
|
632
|
-
* },
|
|
633
|
-
* }
|
|
634
|
-
* })
|
|
635
|
-
* ```
|
|
636
|
-
*/
|
|
637
|
-
function createCache(build) {
|
|
638
|
-
return (options) => build(options ?? {});
|
|
639
|
-
}
|
|
640
|
-
//#endregion
|
|
641
|
-
//#region package.json
|
|
642
|
-
var version = "5.0.0-beta.54";
|
|
643
|
-
//#endregion
|
|
644
608
|
//#region src/diagnostics.ts
|
|
645
609
|
/**
|
|
646
610
|
* Docs major, derived from the package version so the link tracks the published major.
|
|
647
611
|
*/
|
|
648
|
-
const docsMajor =
|
|
612
|
+
const docsMajor = "5.0.0-beta.56".split(".")[0] ?? "5";
|
|
649
613
|
/**
|
|
650
614
|
* Narrows a {@link Diagnostic} to the variant for `code`, or `null` when it does not match.
|
|
651
615
|
*
|
|
@@ -1069,88 +1033,6 @@ var Diagnostics = class Diagnostics {
|
|
|
1069
1033
|
}
|
|
1070
1034
|
};
|
|
1071
1035
|
//#endregion
|
|
1072
|
-
//#region src/Fingerprint.ts
|
|
1073
|
-
/**
|
|
1074
|
-
* Computes the cache key for an incremental build. All methods are static, so call them as
|
|
1075
|
-
* `Fingerprint.compute(...)` and `Fingerprint.stringify(...)`. The key holds no absolute
|
|
1076
|
-
* paths or modification times, so it never depends on where the project lives on disk.
|
|
1077
|
-
*/
|
|
1078
|
-
var Fingerprint = class Fingerprint {
|
|
1079
|
-
/**
|
|
1080
|
-
* Bumped when the snapshot format or fingerprint inputs change in an incompatible way, so stale
|
|
1081
|
-
* cache entries from older Kubb builds are never reused.
|
|
1082
|
-
*/
|
|
1083
|
-
static version = 1;
|
|
1084
|
-
/**
|
|
1085
|
-
* Deterministically serializes a value to JSON: object keys are sorted recursively and
|
|
1086
|
-
* `undefined` values and functions are dropped. Two structurally equal configs produce the same
|
|
1087
|
-
* string regardless of key order, which keeps the fingerprint stable across machines.
|
|
1088
|
-
*/
|
|
1089
|
-
static stringify(value) {
|
|
1090
|
-
return JSON.stringify(Fingerprint.#normalize(value));
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Computes a cache key from everything that affects the generated output: the spec content, the
|
|
1094
|
-
* output-shaping config, each plugin's name and options, the running
|
|
1095
|
-
* `@kubb/core` version, and the cache format version. Returns `null` when the input can't be
|
|
1096
|
-
* fingerprinted (remote URL or no adapter source), which disables caching for that build.
|
|
1097
|
-
*/
|
|
1098
|
-
static async compute({ config, adapterSource, version }) {
|
|
1099
|
-
if (!adapterSource) return null;
|
|
1100
|
-
const spec = await Fingerprint.#readSpec(adapterSource, config.root);
|
|
1101
|
-
if (spec === null) return null;
|
|
1102
|
-
const input = {
|
|
1103
|
-
cacheVersion: Fingerprint.version,
|
|
1104
|
-
version,
|
|
1105
|
-
spec,
|
|
1106
|
-
name: config.name,
|
|
1107
|
-
output: config.output,
|
|
1108
|
-
adapter: config.adapter?.name,
|
|
1109
|
-
parsers: config.parsers.map((parser) => parser.name),
|
|
1110
|
-
plugins: config.plugins.map((plugin) => ({
|
|
1111
|
-
name: plugin.name,
|
|
1112
|
-
options: plugin.options
|
|
1113
|
-
}))
|
|
1114
|
-
};
|
|
1115
|
-
return (0, node_crypto.createHash)("sha256").update(Fingerprint.stringify(input)).digest("hex");
|
|
1116
|
-
}
|
|
1117
|
-
static #normalize(value) {
|
|
1118
|
-
if (value === null || typeof value !== "object") return typeof value === "function" ? void 0 : value;
|
|
1119
|
-
if (Array.isArray(value)) return value.map((item) => Fingerprint.#normalize(item));
|
|
1120
|
-
const source = value;
|
|
1121
|
-
const result = {};
|
|
1122
|
-
for (const key of Object.keys(source).sort()) {
|
|
1123
|
-
const normalized = Fingerprint.#normalize(source[key]);
|
|
1124
|
-
if (normalized !== void 0) result[key] = normalized;
|
|
1125
|
-
}
|
|
1126
|
-
return result;
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* Reads the spec content that feeds the fingerprint. Returns `null` for a remote URL source
|
|
1130
|
-
* (hashing remote content would mean fetching it on every run) or when a file can't be read, so a
|
|
1131
|
-
* missing or virtual spec disables caching instead of failing the build.
|
|
1132
|
-
*/
|
|
1133
|
-
static async #readSpec(source, root) {
|
|
1134
|
-
if (source.type === "data") return {
|
|
1135
|
-
kind: "data",
|
|
1136
|
-
data: typeof source.data === "string" ? source.data : Fingerprint.stringify(source.data)
|
|
1137
|
-
};
|
|
1138
|
-
const paths = source.type === "paths" ? source.paths : [source.path];
|
|
1139
|
-
if (paths.some((path) => new URLPath(path).isURL)) return null;
|
|
1140
|
-
try {
|
|
1141
|
-
return {
|
|
1142
|
-
kind: "path",
|
|
1143
|
-
contents: await Promise.all(paths.map(async (path) => ({
|
|
1144
|
-
path: (0, node_path.relative)(root, path),
|
|
1145
|
-
content: await (0, node_fs_promises.readFile)(path, "utf8")
|
|
1146
|
-
})))
|
|
1147
|
-
};
|
|
1148
|
-
} catch {
|
|
1149
|
-
return null;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
};
|
|
1153
|
-
//#endregion
|
|
1154
1036
|
//#region src/definePlugin.ts
|
|
1155
1037
|
/**
|
|
1156
1038
|
* Merges the `output.mode` default into the output config and validates the combination.
|
|
@@ -1254,12 +1136,12 @@ function matchesSchemaPattern(node, type, pattern) {
|
|
|
1254
1136
|
/**
|
|
1255
1137
|
* Default name resolver used by `defineResolver`.
|
|
1256
1138
|
*
|
|
1257
|
-
* - `camelCase` for `
|
|
1139
|
+
* - `camelCase` for `file`, with dotted names split into `/`-joined nested paths.
|
|
1258
1140
|
* - `PascalCase` for `type`.
|
|
1259
|
-
* - `camelCase` for everything else.
|
|
1141
|
+
* - `camelCase` for `function` and everything else.
|
|
1260
1142
|
*/
|
|
1261
1143
|
function defaultResolver(name, type) {
|
|
1262
|
-
if (type === "file"
|
|
1144
|
+
if (type === "file") return toFilePath(name);
|
|
1263
1145
|
if (type === "type") return require_memoryStorage.pascalCase(name);
|
|
1264
1146
|
return require_memoryStorage.camelCase(name);
|
|
1265
1147
|
}
|
|
@@ -1678,8 +1560,7 @@ var KubbDriver = class {
|
|
|
1678
1560
|
config;
|
|
1679
1561
|
options;
|
|
1680
1562
|
/**
|
|
1681
|
-
* The streaming `
|
|
1682
|
-
* Always set after adapter setup, parse-only adapters are wrapped automatically.
|
|
1563
|
+
* The streaming `InputNode<true>` produced by the adapter. * Always set after adapter setup, parse-only adapters are wrapped automatically.
|
|
1683
1564
|
*/
|
|
1684
1565
|
inputNode = null;
|
|
1685
1566
|
adapter = null;
|
|
@@ -1936,11 +1817,8 @@ var KubbDriver = class {
|
|
|
1936
1817
|
await hooks.emit("kubb:files:processing:start", { files });
|
|
1937
1818
|
});
|
|
1938
1819
|
const updateBuffer = [];
|
|
1939
|
-
const cacheEnabled = Boolean(config.cache);
|
|
1940
|
-
const snapshotSources = /* @__PURE__ */ new Map();
|
|
1941
1820
|
processor.hooks.on("update", (item) => {
|
|
1942
1821
|
updateBuffer.push(item);
|
|
1943
|
-
if (cacheEnabled && item.source !== void 0) snapshotSources.set(item.file.path, item.source);
|
|
1944
1822
|
});
|
|
1945
1823
|
processor.hooks.on("end", async (files) => {
|
|
1946
1824
|
await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
|
|
@@ -1956,19 +1834,7 @@ var KubbDriver = class {
|
|
|
1956
1834
|
this.fileManager.hooks.on("upsert", onFileUpsert);
|
|
1957
1835
|
return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
|
|
1958
1836
|
try {
|
|
1959
|
-
const cache = config.cache;
|
|
1960
1837
|
const outputRoot = (0, node_path.resolve)(config.root, config.output.path);
|
|
1961
|
-
const cacheKey = cache ? await Fingerprint.compute({
|
|
1962
|
-
config,
|
|
1963
|
-
adapterSource: this.#adapterSource,
|
|
1964
|
-
version
|
|
1965
|
-
}) : null;
|
|
1966
|
-
if (cache && cacheKey && await this.#restoreSnapshot({
|
|
1967
|
-
cache,
|
|
1968
|
-
cacheKey,
|
|
1969
|
-
outputRoot,
|
|
1970
|
-
storage
|
|
1971
|
-
})) return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
1972
1838
|
await this.#parseInput();
|
|
1973
1839
|
await this.emitSetupHooks();
|
|
1974
1840
|
if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
|
|
@@ -2029,12 +1895,6 @@ var KubbDriver = class {
|
|
|
2029
1895
|
config,
|
|
2030
1896
|
outputDir: outputRoot
|
|
2031
1897
|
});
|
|
2032
|
-
if (cache && cacheKey && !Diagnostics.hasError(diagnostics)) await this.#persistSnapshot({
|
|
2033
|
-
cache,
|
|
2034
|
-
cacheKey,
|
|
2035
|
-
outputRoot,
|
|
2036
|
-
sources: snapshotSources
|
|
2037
|
-
});
|
|
2038
1898
|
return { diagnostics: Diagnostics.dedupe(diagnostics) };
|
|
2039
1899
|
} catch (caughtError) {
|
|
2040
1900
|
diagnostics.push(Diagnostics.from(caughtError));
|
|
@@ -2044,43 +1904,6 @@ var KubbDriver = class {
|
|
|
2044
1904
|
}
|
|
2045
1905
|
});
|
|
2046
1906
|
}
|
|
2047
|
-
/**
|
|
2048
|
-
* Writes a restored snapshot straight to storage and emits `kubb:build:end`. Returns `true` on a
|
|
2049
|
-
* hit (the build is done), `false` on a miss so the caller falls through to a full build.
|
|
2050
|
-
*/
|
|
2051
|
-
async #restoreSnapshot({ cache, cacheKey, outputRoot, storage }) {
|
|
2052
|
-
const snapshot = await cache.restore({ key: cacheKey });
|
|
2053
|
-
if (!snapshot) return false;
|
|
2054
|
-
const queue = [];
|
|
2055
|
-
for (const [relativePath, source] of Object.entries(snapshot.files)) {
|
|
2056
|
-
const absolutePath = (0, node_path.join)(outputRoot, relativePath);
|
|
2057
|
-
this.fileManager.upsert((0, _kubb_ast.createFile)({
|
|
2058
|
-
path: absolutePath,
|
|
2059
|
-
baseName: (0, node_path.basename)(relativePath)
|
|
2060
|
-
}));
|
|
2061
|
-
queue.push(storage.setItem(absolutePath, source));
|
|
2062
|
-
if (queue.length >= 50) await Promise.all(queue.splice(0));
|
|
2063
|
-
}
|
|
2064
|
-
await Promise.all(queue);
|
|
2065
|
-
await this.hooks.emit("kubb:build:end", {
|
|
2066
|
-
files: this.fileManager.files,
|
|
2067
|
-
config: this.config,
|
|
2068
|
-
outputDir: outputRoot
|
|
2069
|
-
});
|
|
2070
|
-
return true;
|
|
2071
|
-
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Stores this run's rendered output, keyed by the input fingerprint, so the next unchanged build
|
|
2074
|
-
* restores it instead of regenerating. `sources` is keyed by absolute path and relativized here.
|
|
2075
|
-
*/
|
|
2076
|
-
async #persistSnapshot({ cache, cacheKey, outputRoot, sources }) {
|
|
2077
|
-
const files = {};
|
|
2078
|
-
for (const [absolutePath, source] of sources) files[(0, node_path.relative)(outputRoot, absolutePath)] = source;
|
|
2079
|
-
await cache.persist({
|
|
2080
|
-
key: cacheKey,
|
|
2081
|
-
snapshot: { files }
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
1907
|
#filesPayload() {
|
|
2085
1908
|
const driver = this;
|
|
2086
1909
|
return {
|
|
@@ -2464,7 +2287,7 @@ function inputToAdapterSource(config) {
|
|
|
2464
2287
|
type: "data",
|
|
2465
2288
|
data: input.data
|
|
2466
2289
|
};
|
|
2467
|
-
if (
|
|
2290
|
+
if (Url.canParse(input.path)) return {
|
|
2468
2291
|
type: "path",
|
|
2469
2292
|
path: input.path
|
|
2470
2293
|
};
|
|
@@ -2482,11 +2305,11 @@ function inputToAdapterSource(config) {
|
|
|
2482
2305
|
* Keys are resolved against `process.cwd()`, so root-relative paths such as
|
|
2483
2306
|
* `src/gen/api/getPets.ts` are written to the correct location without extra configuration.
|
|
2484
2307
|
*
|
|
2485
|
-
*
|
|
2486
|
-
* -
|
|
2487
|
-
* -
|
|
2488
|
-
* -
|
|
2489
|
-
* -
|
|
2308
|
+
* Writes are deduplicated and directory-safe:
|
|
2309
|
+
* - leading and trailing whitespace is trimmed before writing
|
|
2310
|
+
* - the write is skipped when the file content is already identical
|
|
2311
|
+
* - missing parent directories are created automatically
|
|
2312
|
+
* - Bun's native file API is used when running under Bun
|
|
2490
2313
|
*
|
|
2491
2314
|
* @example
|
|
2492
2315
|
* ```ts
|
|
@@ -2525,7 +2348,7 @@ const fsStorage = require_memoryStorage.createStorage(() => ({
|
|
|
2525
2348
|
},
|
|
2526
2349
|
async getKeys(base) {
|
|
2527
2350
|
const resolvedBase = (0, node_path.resolve)(base ?? process.cwd());
|
|
2528
|
-
if (isBun
|
|
2351
|
+
if (runtime.isBun) {
|
|
2529
2352
|
const bunGlob = new Bun.Glob("**/*");
|
|
2530
2353
|
return Array.fromAsync(bunGlob.scan({
|
|
2531
2354
|
cwd: resolvedBase,
|
|
@@ -2598,7 +2421,6 @@ function resolveConfig(userConfig) {
|
|
|
2598
2421
|
...userConfig.output
|
|
2599
2422
|
},
|
|
2600
2423
|
storage: userConfig.storage ?? fsStorage(),
|
|
2601
|
-
cache: userConfig.cache === false ? void 0 : userConfig.cache,
|
|
2602
2424
|
reporters: userConfig.reporters ?? [],
|
|
2603
2425
|
plugins: userConfig.plugins ?? []
|
|
2604
2426
|
};
|
|
@@ -3061,134 +2883,10 @@ function defineParser(parser) {
|
|
|
3061
2883
|
return parser;
|
|
3062
2884
|
}
|
|
3063
2885
|
//#endregion
|
|
3064
|
-
//#region src/caches/fsCache.ts
|
|
3065
|
-
/**
|
|
3066
|
-
* Reads and prunes the local cache manifest. All methods are static, so call them as
|
|
3067
|
-
* `Manifest.read(dir)` and `Manifest.prune(data, ...)`. A damaged manifest reads as empty so the
|
|
3068
|
-
* cache degrades to misses instead of throwing. Writing goes through `write` from `@internals/utils`.
|
|
3069
|
-
*/
|
|
3070
|
-
var Manifest = class Manifest {
|
|
3071
|
-
/**
|
|
3072
|
-
* On-disk layout version for the manifest itself. Bumped when the manifest shape changes; a
|
|
3073
|
-
* mismatch makes the whole local cache read as empty.
|
|
3074
|
-
*/
|
|
3075
|
-
static version = 1;
|
|
3076
|
-
/**
|
|
3077
|
-
* Reads the manifest at `dir/manifest.json`. A missing, corrupt, or version-mismatched file reads
|
|
3078
|
-
* as an empty manifest.
|
|
3079
|
-
*/
|
|
3080
|
-
static async read(dir) {
|
|
3081
|
-
try {
|
|
3082
|
-
const parsed = JSON.parse(await read((0, node_path.join)(dir, "manifest.json")));
|
|
3083
|
-
if (parsed.version !== Manifest.version || typeof parsed.entries !== "object") return Manifest.#empty();
|
|
3084
|
-
return parsed;
|
|
3085
|
-
} catch {
|
|
3086
|
-
return Manifest.#empty();
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3089
|
-
/**
|
|
3090
|
-
* Selects the keys to evict so the cache stays within `ttlDays` and `maxEntries`. Returns the
|
|
3091
|
-
* surviving manifest plus the evicted keys (the caller deletes their blobs). Pure, does no IO.
|
|
3092
|
-
*/
|
|
3093
|
-
static prune(manifest, { maxEntries, ttlDays, now }) {
|
|
3094
|
-
const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
|
|
3095
|
-
const removed = [];
|
|
3096
|
-
const kept = [];
|
|
3097
|
-
for (const [key, entry] of Object.entries(manifest.entries)) if (now - entry.lastAccess > ttlMs) removed.push(key);
|
|
3098
|
-
else kept.push([key, entry]);
|
|
3099
|
-
if (kept.length > maxEntries) {
|
|
3100
|
-
kept.sort((a, b) => b[1].lastAccess - a[1].lastAccess);
|
|
3101
|
-
for (const [key] of kept.splice(maxEntries)) removed.push(key);
|
|
3102
|
-
}
|
|
3103
|
-
return {
|
|
3104
|
-
manifest: {
|
|
3105
|
-
version: Manifest.version,
|
|
3106
|
-
entries: Object.fromEntries(kept)
|
|
3107
|
-
},
|
|
3108
|
-
removed
|
|
3109
|
-
};
|
|
3110
|
-
}
|
|
3111
|
-
static #empty() {
|
|
3112
|
-
return {
|
|
3113
|
-
version: Manifest.version,
|
|
3114
|
-
entries: {}
|
|
3115
|
-
};
|
|
3116
|
-
}
|
|
3117
|
-
};
|
|
3118
|
-
function blobName(relativePath) {
|
|
3119
|
-
return `${(0, node_crypto.createHash)("sha256").update(relativePath).digest("hex")}.blob`;
|
|
3120
|
-
}
|
|
3121
|
-
/**
|
|
3122
|
-
* Local filesystem cache. Stores each build snapshot as content blobs plus an index,
|
|
3123
|
-
* tracked by a manifest under `node_modules/.cache/kubb/` (the Nx and Vitest
|
|
3124
|
-
* convention). Least-recently-used and expired entries are pruned on every persist.
|
|
3125
|
-
*
|
|
3126
|
-
* @example
|
|
3127
|
-
* ```ts
|
|
3128
|
-
* import { fsCache } from '@kubb/core'
|
|
3129
|
-
*
|
|
3130
|
-
* export default defineConfig({
|
|
3131
|
-
* cache: fsCache(),
|
|
3132
|
-
* })
|
|
3133
|
-
* ```
|
|
3134
|
-
*/
|
|
3135
|
-
const fsCache = createCache((options = {}) => {
|
|
3136
|
-
const dir = (0, node_path.resolve)(options.dir ?? (0, node_path.join)("node_modules", ".cache", "kubb"));
|
|
3137
|
-
const maxEntries = options.maxEntries ?? 50;
|
|
3138
|
-
const ttlDays = options.ttlDays ?? 7;
|
|
3139
|
-
const blobsDir = (0, node_path.join)(dir, "blobs");
|
|
3140
|
-
const manifestPath = (0, node_path.join)(dir, "manifest.json");
|
|
3141
|
-
return {
|
|
3142
|
-
name: "fs",
|
|
3143
|
-
async restore({ key }) {
|
|
3144
|
-
const manifest = await Manifest.read(dir);
|
|
3145
|
-
const entry = manifest.entries[key];
|
|
3146
|
-
if (!entry) return null;
|
|
3147
|
-
try {
|
|
3148
|
-
const index = JSON.parse(await read((0, node_path.join)(blobsDir, key, "index.json")));
|
|
3149
|
-
const files = {};
|
|
3150
|
-
for (const { path, blob } of index) files[path] = await read((0, node_path.join)(blobsDir, key, blob));
|
|
3151
|
-
entry.lastAccess = Date.now();
|
|
3152
|
-
await write(manifestPath, JSON.stringify(manifest)).catch(() => {});
|
|
3153
|
-
return { files };
|
|
3154
|
-
} catch {
|
|
3155
|
-
return null;
|
|
3156
|
-
}
|
|
3157
|
-
},
|
|
3158
|
-
async persist({ key, snapshot }) {
|
|
3159
|
-
const entryDir = (0, node_path.join)(blobsDir, key);
|
|
3160
|
-
const index = [];
|
|
3161
|
-
for (const [path, source] of Object.entries(snapshot.files)) {
|
|
3162
|
-
const blob = blobName(path);
|
|
3163
|
-
await write((0, node_path.join)(entryDir, blob), source);
|
|
3164
|
-
index.push({
|
|
3165
|
-
path,
|
|
3166
|
-
blob
|
|
3167
|
-
});
|
|
3168
|
-
}
|
|
3169
|
-
await write((0, node_path.join)(entryDir, "index.json"), JSON.stringify(index));
|
|
3170
|
-
const manifest = await Manifest.read(dir);
|
|
3171
|
-
const now = Date.now();
|
|
3172
|
-
manifest.entries[key] = {
|
|
3173
|
-
files: index.map((item) => item.path),
|
|
3174
|
-
createdAt: now,
|
|
3175
|
-
lastAccess: now
|
|
3176
|
-
};
|
|
3177
|
-
const pruned = Manifest.prune(manifest, {
|
|
3178
|
-
maxEntries,
|
|
3179
|
-
ttlDays,
|
|
3180
|
-
now
|
|
3181
|
-
});
|
|
3182
|
-
await Promise.all(pruned.removed.map((removedKey) => clean((0, node_path.join)(blobsDir, removedKey))));
|
|
3183
|
-
await write(manifestPath, JSON.stringify(pruned.manifest));
|
|
3184
|
-
}
|
|
3185
|
-
};
|
|
3186
|
-
});
|
|
3187
|
-
//#endregion
|
|
3188
2886
|
exports.AsyncEventEmitter = require_memoryStorage.AsyncEventEmitter;
|
|
3189
2887
|
exports.Diagnostics = Diagnostics;
|
|
3190
2888
|
exports.KubbDriver = KubbDriver;
|
|
3191
|
-
exports.
|
|
2889
|
+
exports.Url = Url;
|
|
3192
2890
|
Object.defineProperty(exports, "ast", {
|
|
3193
2891
|
enumerable: true,
|
|
3194
2892
|
get: function() {
|
|
@@ -3197,7 +2895,6 @@ Object.defineProperty(exports, "ast", {
|
|
|
3197
2895
|
});
|
|
3198
2896
|
exports.cliReporter = cliReporter;
|
|
3199
2897
|
exports.createAdapter = createAdapter;
|
|
3200
|
-
exports.createCache = createCache;
|
|
3201
2898
|
exports.createKubb = createKubb;
|
|
3202
2899
|
exports.createRenderer = createRenderer;
|
|
3203
2900
|
exports.createReporter = createReporter;
|
|
@@ -3207,7 +2904,6 @@ exports.defineParser = defineParser;
|
|
|
3207
2904
|
exports.definePlugin = definePlugin;
|
|
3208
2905
|
exports.defineResolver = defineResolver;
|
|
3209
2906
|
exports.fileReporter = fileReporter;
|
|
3210
|
-
exports.fsCache = fsCache;
|
|
3211
2907
|
exports.fsStorage = fsStorage;
|
|
3212
2908
|
exports.jsonReporter = jsonReporter;
|
|
3213
2909
|
exports.logLevel = logLevel;
|