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