@kubb/core 5.0.0-alpha.2 → 5.0.0-alpha.21
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/{types-B7eZvqwD.d.ts → PluginDriver-CEQPafXV.d.ts} +687 -298
- package/dist/hooks.cjs +15 -9
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +11 -5
- package/dist/hooks.js +16 -10
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +1131 -536
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +674 -89
- package/dist/index.js +1114 -532
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/Kubb.ts +37 -55
- package/src/{PluginManager.ts → PluginDriver.ts} +51 -40
- package/src/build.ts +74 -29
- package/src/config.ts +9 -8
- package/src/constants.ts +44 -1
- package/src/createAdapter.ts +25 -0
- package/src/createPlugin.ts +28 -0
- package/src/createStorage.ts +58 -0
- package/src/defineBuilder.ts +26 -0
- package/src/defineGenerator.ts +137 -0
- package/src/defineLogger.ts +13 -3
- package/src/definePreset.ts +27 -0
- package/src/definePresets.ts +16 -0
- package/src/defineResolver.ts +448 -0
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useDriver.ts +8 -0
- package/src/hooks/useMode.ts +5 -2
- package/src/hooks/usePlugin.ts +5 -2
- package/src/index.ts +21 -6
- package/src/renderNode.tsx +105 -0
- package/src/storages/fsStorage.ts +2 -2
- package/src/storages/memoryStorage.ts +2 -2
- package/src/types.ts +342 -42
- package/src/utils/FunctionParams.ts +2 -2
- package/src/utils/TreeNode.ts +24 -1
- package/src/utils/diagnostics.ts +4 -1
- package/src/utils/executeStrategies.ts +23 -10
- package/src/utils/formatters.ts +10 -21
- package/src/utils/getBarrelFiles.ts +79 -9
- package/src/utils/getConfigs.ts +8 -22
- package/src/utils/getPreset.ts +52 -0
- package/src/utils/linters.ts +23 -3
- package/src/utils/mergeResolvers.ts +8 -0
- package/src/utils/packageJSON.ts +76 -0
- package/src/BarrelManager.ts +0 -74
- package/src/PackageManager.ts +0 -180
- package/src/PromiseManager.ts +0 -40
- package/src/defineAdapter.ts +0 -22
- package/src/definePlugin.ts +0 -12
- package/src/defineStorage.ts +0 -56
- package/src/errors.ts +0 -1
- package/src/hooks/usePluginManager.ts +0 -8
- package/src/utils/getPlugins.ts +0 -23
package/dist/index.js
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
|
-
import mod from "node:module";
|
|
3
2
|
import { EventEmitter } from "node:events";
|
|
4
|
-
import { parseArgs, styleText } from "node:util";
|
|
5
3
|
import { readFileSync } from "node:fs";
|
|
6
4
|
import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
7
|
-
import path, { dirname, join, posix, relative, resolve } from "node:path";
|
|
8
|
-
import { definePrinter } from "@kubb/ast";
|
|
9
|
-
import { createFabric } from "@kubb/react-fabric";
|
|
5
|
+
import path, { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
|
|
6
|
+
import { definePrinter, isOperationNode, isSchemaNode } from "@kubb/ast";
|
|
7
|
+
import { Fabric, createFabric, createReactFabric } from "@kubb/react-fabric";
|
|
10
8
|
import { typescriptParser } from "@kubb/react-fabric/parsers";
|
|
11
9
|
import { fsPlugin } from "@kubb/react-fabric/plugins";
|
|
12
10
|
import { performance } from "node:perf_hooks";
|
|
13
11
|
import { deflateSync } from "fflate";
|
|
14
12
|
import { x } from "tinyexec";
|
|
15
13
|
import { version } from "node:process";
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
14
|
+
import { jsx } from "@kubb/react-fabric/jsx-runtime";
|
|
15
|
+
import { sortBy } from "remeda";
|
|
18
16
|
import * as pkg from "empathic/package";
|
|
19
17
|
import { coerce, satisfies } from "semver";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
//#region ../../internals/utils/src/errors.ts
|
|
19
|
+
/** Thrown when a plugin's configuration or input fails validation.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* throw new ValidationPluginError('Invalid config: "output.path" is required')
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
23
26
|
var ValidationPluginError = class extends Error {};
|
|
24
27
|
/**
|
|
25
28
|
* Thrown when one or more errors occur during a Kubb build.
|
|
26
29
|
* Carries the full list of underlying errors on `errors`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* throw new BuildError('Build failed', { errors: [err1, err2] })
|
|
34
|
+
* ```
|
|
27
35
|
*/
|
|
28
36
|
var BuildError = class extends Error {
|
|
29
37
|
errors;
|
|
@@ -35,19 +43,34 @@ var BuildError = class extends Error {
|
|
|
35
43
|
};
|
|
36
44
|
/**
|
|
37
45
|
* Coerces an unknown thrown value to an `Error` instance.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
46
|
+
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* try { ... } catch(err) {
|
|
51
|
+
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
40
54
|
*/
|
|
41
55
|
function toError(value) {
|
|
42
56
|
return value instanceof Error ? value : new Error(String(value));
|
|
43
57
|
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region ../../internals/utils/src/asyncEventEmitter.ts
|
|
44
60
|
/**
|
|
45
|
-
*
|
|
61
|
+
* Typed `EventEmitter` that awaits all async listeners before resolving.
|
|
46
62
|
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
|
|
67
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
68
|
+
* await emitter.emit('build', 'petstore') // all listeners awaited
|
|
69
|
+
* ```
|
|
47
70
|
*/
|
|
48
71
|
var AsyncEventEmitter = class {
|
|
49
72
|
/**
|
|
50
|
-
*
|
|
73
|
+
* Maximum number of listeners per event before Node emits a memory-leak warning.
|
|
51
74
|
* @default 10
|
|
52
75
|
*/
|
|
53
76
|
constructor(maxListener = 10) {
|
|
@@ -55,8 +78,13 @@ var AsyncEventEmitter = class {
|
|
|
55
78
|
}
|
|
56
79
|
#emitter = new EventEmitter();
|
|
57
80
|
/**
|
|
58
|
-
* Emits
|
|
81
|
+
* Emits `eventName` and awaits all registered listeners in parallel.
|
|
59
82
|
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* await emitter.emit('build', 'petstore')
|
|
87
|
+
* ```
|
|
60
88
|
*/
|
|
61
89
|
async emit(eventName, ...eventArgs) {
|
|
62
90
|
const listeners = this.#emitter.listeners(eventName);
|
|
@@ -75,11 +103,25 @@ var AsyncEventEmitter = class {
|
|
|
75
103
|
}
|
|
76
104
|
}));
|
|
77
105
|
}
|
|
78
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Registers a persistent listener for `eventName`.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* emitter.on('build', async (name) => { console.log(name) })
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
79
114
|
on(eventName, handler) {
|
|
80
115
|
this.#emitter.on(eventName, handler);
|
|
81
116
|
}
|
|
82
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Registers a one-shot listener that removes itself after the first invocation.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* emitter.onOnce('build', async (name) => { console.log(name) })
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
83
125
|
onOnce(eventName, handler) {
|
|
84
126
|
const wrapper = (...args) => {
|
|
85
127
|
this.off(eventName, wrapper);
|
|
@@ -87,15 +129,31 @@ var AsyncEventEmitter = class {
|
|
|
87
129
|
};
|
|
88
130
|
this.on(eventName, wrapper);
|
|
89
131
|
}
|
|
90
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Removes a previously registered listener.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* emitter.off('build', handler)
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
91
140
|
off(eventName, handler) {
|
|
92
141
|
this.#emitter.off(eventName, handler);
|
|
93
142
|
}
|
|
94
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Removes all listeners from every event channel.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* emitter.removeAll()
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
95
151
|
removeAll() {
|
|
96
152
|
this.#emitter.removeAllListeners();
|
|
97
153
|
}
|
|
98
154
|
};
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region ../../internals/utils/src/casing.ts
|
|
99
157
|
/**
|
|
100
158
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
101
159
|
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
@@ -114,9 +172,12 @@ function toCamelOrPascal(text, pascal) {
|
|
|
114
172
|
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
115
173
|
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
116
174
|
* Segments are joined with `/` to form a file path.
|
|
175
|
+
*
|
|
176
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
177
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
117
178
|
*/
|
|
118
179
|
function applyToFileParts(text, transformPart) {
|
|
119
|
-
const parts = text.split(
|
|
180
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
120
181
|
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
121
182
|
}
|
|
122
183
|
/**
|
|
@@ -134,190 +195,33 @@ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
134
195
|
} : {}));
|
|
135
196
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
136
197
|
}
|
|
137
|
-
/** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
|
|
138
|
-
function defineCLIAdapter(adapter) {
|
|
139
|
-
return adapter;
|
|
140
|
-
}
|
|
141
198
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
199
|
+
* Converts `text` to PascalCase.
|
|
200
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
204
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
144
205
|
*/
|
|
145
|
-
function
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
description: def.description,
|
|
152
|
-
arguments: def.arguments,
|
|
153
|
-
options: serializeOptions(def.options ?? {}),
|
|
154
|
-
subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
function serializeOptions(options) {
|
|
158
|
-
return Object.entries(options).map(([name, opt]) => {
|
|
159
|
-
return {
|
|
160
|
-
name,
|
|
161
|
-
flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
|
|
162
|
-
type: opt.type,
|
|
163
|
-
description: opt.description,
|
|
164
|
-
...opt.default !== void 0 ? { default: opt.default } : {},
|
|
165
|
-
...opt.hint ? { hint: opt.hint } : {},
|
|
166
|
-
...opt.enum ? { enum: opt.enum } : {},
|
|
167
|
-
...opt.required ? { required: opt.required } : {}
|
|
168
|
-
};
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
/** Prints formatted help output for a command using its `CommandDefinition`. */
|
|
172
|
-
function renderHelp(def, parentName) {
|
|
173
|
-
const schema = getCommandSchema([def])[0];
|
|
174
|
-
const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
|
|
175
|
-
const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
|
|
176
|
-
const subCmdPart = schema.subCommands.length ? " <command>" : "";
|
|
177
|
-
console.log(`\n${styleText("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
|
|
178
|
-
if (schema.description) console.log(` ${schema.description}\n`);
|
|
179
|
-
if (schema.subCommands.length) {
|
|
180
|
-
console.log(styleText("bold", "Commands:"));
|
|
181
|
-
for (const sub of schema.subCommands) console.log(` ${styleText("cyan", sub.name.padEnd(16))}${sub.description}`);
|
|
182
|
-
console.log();
|
|
183
|
-
}
|
|
184
|
-
const options = [...schema.options, {
|
|
185
|
-
name: "help",
|
|
186
|
-
flags: "-h, --help",
|
|
187
|
-
type: "boolean",
|
|
188
|
-
description: "Show help"
|
|
189
|
-
}];
|
|
190
|
-
console.log(styleText("bold", "Options:"));
|
|
191
|
-
for (const opt of options) {
|
|
192
|
-
const flags = styleText("cyan", opt.flags.padEnd(30));
|
|
193
|
-
const defaultPart = opt.default !== void 0 ? styleText("dim", ` (default: ${opt.default})`) : "";
|
|
194
|
-
console.log(` ${flags}${opt.description}${defaultPart}`);
|
|
195
|
-
}
|
|
196
|
-
console.log();
|
|
197
|
-
}
|
|
198
|
-
function buildParseOptions(def) {
|
|
199
|
-
const result = { help: {
|
|
200
|
-
type: "boolean",
|
|
201
|
-
short: "h"
|
|
202
|
-
} };
|
|
203
|
-
for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
|
|
204
|
-
type: opt.type,
|
|
205
|
-
...opt.short ? { short: opt.short } : {},
|
|
206
|
-
...opt.default !== void 0 ? { default: opt.default } : {}
|
|
207
|
-
};
|
|
208
|
-
return result;
|
|
206
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
207
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
208
|
+
prefix,
|
|
209
|
+
suffix
|
|
210
|
+
}) : camelCase(part));
|
|
211
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
209
212
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let parsed;
|
|
213
|
-
try {
|
|
214
|
-
const result = parseArgs({
|
|
215
|
-
args: argv,
|
|
216
|
-
options: parseOptions,
|
|
217
|
-
allowPositionals: true,
|
|
218
|
-
strict: false
|
|
219
|
-
});
|
|
220
|
-
parsed = {
|
|
221
|
-
values: result.values,
|
|
222
|
-
positionals: result.positionals
|
|
223
|
-
};
|
|
224
|
-
} catch {
|
|
225
|
-
renderHelp(def, parentName);
|
|
226
|
-
process.exit(1);
|
|
227
|
-
}
|
|
228
|
-
if (parsed.values["help"]) {
|
|
229
|
-
renderHelp(def, parentName);
|
|
230
|
-
process.exit(0);
|
|
231
|
-
}
|
|
232
|
-
for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
|
|
233
|
-
console.error(styleText("red", `Error: --${name} is required`));
|
|
234
|
-
renderHelp(def, parentName);
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
|
-
if (!def.run) {
|
|
238
|
-
renderHelp(def, parentName);
|
|
239
|
-
process.exit(0);
|
|
240
|
-
}
|
|
241
|
-
try {
|
|
242
|
-
await def.run(parsed);
|
|
243
|
-
} catch (err) {
|
|
244
|
-
console.error(styleText("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
245
|
-
renderHelp(def, parentName);
|
|
246
|
-
process.exit(1);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
function printRootHelp(programName, version, defs) {
|
|
250
|
-
console.log(`\n${styleText("bold", "Usage:")} ${programName} <command> [options]\n`);
|
|
251
|
-
console.log(` Kubb generation — v${version}\n`);
|
|
252
|
-
console.log(styleText("bold", "Commands:"));
|
|
253
|
-
for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
|
|
254
|
-
console.log();
|
|
255
|
-
console.log(styleText("bold", "Options:"));
|
|
256
|
-
console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
|
|
257
|
-
console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
|
|
258
|
-
console.log();
|
|
259
|
-
console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
|
|
260
|
-
}
|
|
261
|
-
defineCLIAdapter({
|
|
262
|
-
renderHelp(def, parentName) {
|
|
263
|
-
renderHelp(def, parentName);
|
|
264
|
-
},
|
|
265
|
-
async run(defs, argv, opts) {
|
|
266
|
-
const { programName, defaultCommandName, version } = opts;
|
|
267
|
-
const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
|
|
268
|
-
if (args[0] === "--version" || args[0] === "-v") {
|
|
269
|
-
console.log(version);
|
|
270
|
-
process.exit(0);
|
|
271
|
-
}
|
|
272
|
-
if (args[0] === "--help" || args[0] === "-h") {
|
|
273
|
-
printRootHelp(programName, version, defs);
|
|
274
|
-
process.exit(0);
|
|
275
|
-
}
|
|
276
|
-
if (args.length === 0) {
|
|
277
|
-
const defaultDef = defs.find((d) => d.name === defaultCommandName);
|
|
278
|
-
if (defaultDef?.run) await runCommand(defaultDef, [], programName);
|
|
279
|
-
else printRootHelp(programName, version, defs);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const [first, ...rest] = args;
|
|
283
|
-
const isKnownSubcommand = defs.some((d) => d.name === first);
|
|
284
|
-
let def;
|
|
285
|
-
let commandArgv;
|
|
286
|
-
let parentName;
|
|
287
|
-
if (isKnownSubcommand) {
|
|
288
|
-
def = defs.find((d) => d.name === first);
|
|
289
|
-
commandArgv = rest;
|
|
290
|
-
parentName = programName;
|
|
291
|
-
} else {
|
|
292
|
-
def = defs.find((d) => d.name === defaultCommandName);
|
|
293
|
-
commandArgv = args;
|
|
294
|
-
parentName = programName;
|
|
295
|
-
}
|
|
296
|
-
if (!def) {
|
|
297
|
-
console.error(`Unknown command: ${first}`);
|
|
298
|
-
printRootHelp(programName, version, defs);
|
|
299
|
-
process.exit(1);
|
|
300
|
-
}
|
|
301
|
-
if (def.subCommands?.length) {
|
|
302
|
-
const [subName, ...subRest] = commandArgv;
|
|
303
|
-
const subDef = def.subCommands.find((s) => s.name === subName);
|
|
304
|
-
if (subName === "--help" || subName === "-h") {
|
|
305
|
-
renderHelp(def, parentName);
|
|
306
|
-
process.exit(0);
|
|
307
|
-
}
|
|
308
|
-
if (!subDef) {
|
|
309
|
-
renderHelp(def, parentName);
|
|
310
|
-
process.exit(subName ? 1 : 0);
|
|
311
|
-
}
|
|
312
|
-
await runCommand(subDef, subRest, `${parentName} ${def.name}`);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
await runCommand(def, commandArgv, parentName);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region ../../internals/utils/src/time.ts
|
|
318
215
|
/**
|
|
319
|
-
* Calculates elapsed time in milliseconds from a high-resolution start time.
|
|
320
|
-
* Rounds to 2 decimal places
|
|
216
|
+
* Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
|
|
217
|
+
* Rounds to 2 decimal places for sub-millisecond precision without noise.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* const start = process.hrtime()
|
|
222
|
+
* doWork()
|
|
223
|
+
* getElapsedMs(start) // 42.35
|
|
224
|
+
* ```
|
|
321
225
|
*/
|
|
322
226
|
function getElapsedMs(hrStart) {
|
|
323
227
|
const [seconds, nanoseconds] = process.hrtime(hrStart);
|
|
@@ -325,39 +229,22 @@ function getElapsedMs(hrStart) {
|
|
|
325
229
|
return Math.round(ms * 100) / 100;
|
|
326
230
|
}
|
|
327
231
|
/**
|
|
328
|
-
* Converts a millisecond duration into a human-readable string.
|
|
329
|
-
*
|
|
232
|
+
* Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* formatMs(250) // '250ms'
|
|
237
|
+
* formatMs(1500) // '1.50s'
|
|
238
|
+
* formatMs(90000) // '1m 30.0s'
|
|
239
|
+
* ```
|
|
330
240
|
*/
|
|
331
241
|
function formatMs(ms) {
|
|
332
242
|
if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
|
|
333
243
|
if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
|
|
334
244
|
return `${Math.round(ms)}ms`;
|
|
335
245
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
* Falls back to `255` for any channel that cannot be parsed.
|
|
339
|
-
*/
|
|
340
|
-
function parseHex(color) {
|
|
341
|
-
const int = Number.parseInt(color.replace("#", ""), 16);
|
|
342
|
-
return Number.isNaN(int) ? {
|
|
343
|
-
r: 255,
|
|
344
|
-
g: 255,
|
|
345
|
-
b: 255
|
|
346
|
-
} : {
|
|
347
|
-
r: int >> 16 & 255,
|
|
348
|
-
g: int >> 8 & 255,
|
|
349
|
-
b: int & 255
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
|
|
354
|
-
* for the given hex color.
|
|
355
|
-
*/
|
|
356
|
-
function hex(color) {
|
|
357
|
-
const { r, g, b } = parseHex(color);
|
|
358
|
-
return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
|
|
359
|
-
}
|
|
360
|
-
hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region ../../internals/utils/src/fs.ts
|
|
361
248
|
/**
|
|
362
249
|
* Converts all backslashes to forward slashes.
|
|
363
250
|
* Extended-length Windows paths (`\\?\...`) are left unchanged.
|
|
@@ -367,8 +254,14 @@ function toSlash(p) {
|
|
|
367
254
|
return p.replaceAll("\\", "/");
|
|
368
255
|
}
|
|
369
256
|
/**
|
|
370
|
-
* Returns the relative path from `rootDir` to `filePath`, always using
|
|
371
|
-
*
|
|
257
|
+
* Returns the relative path from `rootDir` to `filePath`, always using forward slashes
|
|
258
|
+
* and prefixed with `./` when not already traversing upward.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* getRelativePath('/src/components', '/src/components/Button.tsx') // './Button.tsx'
|
|
263
|
+
* getRelativePath('/src/components', '/src/utils/helpers.ts') // '../utils/helpers.ts'
|
|
264
|
+
* ```
|
|
372
265
|
*/
|
|
373
266
|
function getRelativePath(rootDir, filePath) {
|
|
374
267
|
if (!rootDir || !filePath) throw new Error(`Root and file should be filled in when retrieving the relativePath, ${rootDir || ""} ${filePath || ""}`);
|
|
@@ -378,43 +271,54 @@ function getRelativePath(rootDir, filePath) {
|
|
|
378
271
|
/**
|
|
379
272
|
* Resolves to `true` when the file or directory at `path` exists.
|
|
380
273
|
* Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```ts
|
|
277
|
+
* if (await exists('./kubb.config.ts')) {
|
|
278
|
+
* const content = await read('./kubb.config.ts')
|
|
279
|
+
* }
|
|
280
|
+
* ```
|
|
381
281
|
*/
|
|
382
282
|
async function exists(path) {
|
|
383
283
|
if (typeof Bun !== "undefined") return Bun.file(path).exists();
|
|
384
284
|
return access(path).then(() => true, () => false);
|
|
385
285
|
}
|
|
386
286
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
287
|
+
* Synchronous counterpart of `read`.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```ts
|
|
291
|
+
* const source = readSync('./src/Pet.ts')
|
|
292
|
+
* ```
|
|
389
293
|
*/
|
|
390
|
-
async function read(path) {
|
|
391
|
-
if (typeof Bun !== "undefined") return Bun.file(path).text();
|
|
392
|
-
return readFile(path, { encoding: "utf8" });
|
|
393
|
-
}
|
|
394
|
-
/** Synchronous counterpart of `read`. */
|
|
395
294
|
function readSync(path) {
|
|
396
295
|
return readFileSync(path, { encoding: "utf8" });
|
|
397
296
|
}
|
|
398
297
|
/**
|
|
399
298
|
* Writes `data` to `path`, trimming leading/trailing whitespace before saving.
|
|
400
|
-
* Skips the write
|
|
401
|
-
* identical to what is already on disk.
|
|
299
|
+
* Skips the write when the trimmed content is empty or identical to what is already on disk.
|
|
402
300
|
* Creates any missing parent directories automatically.
|
|
403
|
-
* When `sanity` is `true`, re-reads the file after writing and throws if the
|
|
404
|
-
*
|
|
301
|
+
* When `sanity` is `true`, re-reads the file after writing and throws if the content does not match.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* await write('./src/Pet.ts', source) // writes and returns trimmed content
|
|
306
|
+
* await write('./src/Pet.ts', source) // null — file unchanged
|
|
307
|
+
* await write('./src/Pet.ts', ' ') // null — empty content skipped
|
|
308
|
+
* ```
|
|
405
309
|
*/
|
|
406
310
|
async function write(path, data, options = {}) {
|
|
407
311
|
const trimmed = data.trim();
|
|
408
|
-
if (trimmed === "") return
|
|
312
|
+
if (trimmed === "") return null;
|
|
409
313
|
const resolved = resolve(path);
|
|
410
314
|
if (typeof Bun !== "undefined") {
|
|
411
315
|
const file = Bun.file(resolved);
|
|
412
|
-
if ((await file.exists() ? await file.text() : null) === trimmed) return
|
|
316
|
+
if ((await file.exists() ? await file.text() : null) === trimmed) return null;
|
|
413
317
|
await Bun.write(resolved, trimmed);
|
|
414
318
|
return trimmed;
|
|
415
319
|
}
|
|
416
320
|
try {
|
|
417
|
-
if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return
|
|
321
|
+
if (await readFile(resolved, { encoding: "utf-8" }) === trimmed) return null;
|
|
418
322
|
} catch {}
|
|
419
323
|
await mkdir(dirname(resolved), { recursive: true });
|
|
420
324
|
await writeFile(resolved, trimmed, { encoding: "utf-8" });
|
|
@@ -425,16 +329,32 @@ async function write(path, data, options = {}) {
|
|
|
425
329
|
}
|
|
426
330
|
return trimmed;
|
|
427
331
|
}
|
|
428
|
-
/**
|
|
332
|
+
/**
|
|
333
|
+
* Recursively removes `path`. Silently succeeds when `path` does not exist.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* await clean('./dist')
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
429
340
|
async function clean(path) {
|
|
430
341
|
return rm(path, {
|
|
431
342
|
recursive: true,
|
|
432
343
|
force: true
|
|
433
344
|
});
|
|
434
345
|
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region ../../internals/utils/src/names.ts
|
|
435
348
|
/**
|
|
436
349
|
* Registers `originalName` in `data` without altering the returned name.
|
|
437
|
-
* Use
|
|
350
|
+
* Use when you need to track usage frequency but always emit the original identifier.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* const seen: Record<string, number> = {}
|
|
355
|
+
* setUniqueName('Foo', seen) // 'Foo' (seen = { Foo: 1 })
|
|
356
|
+
* setUniqueName('Foo', seen) // 'Foo' (seen = { Foo: 2 })
|
|
357
|
+
* ```
|
|
438
358
|
*/
|
|
439
359
|
function setUniqueName(originalName, data) {
|
|
440
360
|
let used = data[originalName] || 0;
|
|
@@ -445,11 +365,26 @@ function setUniqueName(originalName, data) {
|
|
|
445
365
|
data[originalName] = 1;
|
|
446
366
|
return originalName;
|
|
447
367
|
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region ../../internals/utils/src/promise.ts
|
|
370
|
+
/** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts
|
|
374
|
+
* const results = await Promise.allSettled([p1, p2])
|
|
375
|
+
* results.filter(isPromiseRejectedResult<Error>).map((r) => r.reason.message)
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
function isPromiseRejectedResult(result) {
|
|
379
|
+
return result.status === "rejected";
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
448
383
|
/**
|
|
449
384
|
* JavaScript and Java reserved words.
|
|
450
385
|
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
451
386
|
*/
|
|
452
|
-
const reservedWords = [
|
|
387
|
+
const reservedWords = new Set([
|
|
453
388
|
"abstract",
|
|
454
389
|
"arguments",
|
|
455
390
|
"boolean",
|
|
@@ -531,18 +466,31 @@ const reservedWords = [
|
|
|
531
466
|
"toString",
|
|
532
467
|
"undefined",
|
|
533
468
|
"valueOf"
|
|
534
|
-
];
|
|
469
|
+
]);
|
|
535
470
|
/**
|
|
536
|
-
* Prefixes
|
|
537
|
-
*
|
|
471
|
+
* Prefixes `word` with `_` when it is a reserved JavaScript/Java identifier or starts with a digit.
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* ```ts
|
|
475
|
+
* transformReservedWord('class') // '_class'
|
|
476
|
+
* transformReservedWord('42foo') // '_42foo'
|
|
477
|
+
* transformReservedWord('status') // 'status'
|
|
478
|
+
* ```
|
|
538
479
|
*/
|
|
539
480
|
function transformReservedWord(word) {
|
|
540
481
|
const firstChar = word.charCodeAt(0);
|
|
541
|
-
if (word && (reservedWords.
|
|
482
|
+
if (word && (reservedWords.has(word) || firstChar >= 48 && firstChar <= 57)) return `_${word}`;
|
|
542
483
|
return word;
|
|
543
484
|
}
|
|
544
485
|
/**
|
|
545
486
|
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* isValidVarName('status') // true
|
|
491
|
+
* isValidVarName('class') // false (reserved word)
|
|
492
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
493
|
+
* ```
|
|
546
494
|
*/
|
|
547
495
|
function isValidVarName(name) {
|
|
548
496
|
try {
|
|
@@ -552,6 +500,8 @@ function isValidVarName(name) {
|
|
|
552
500
|
}
|
|
553
501
|
return true;
|
|
554
502
|
}
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region ../../internals/utils/src/urlPath.ts
|
|
555
505
|
/**
|
|
556
506
|
* Parses and transforms an OpenAPI/Swagger path string into various URL formats.
|
|
557
507
|
*
|
|
@@ -561,18 +511,33 @@ function isValidVarName(name) {
|
|
|
561
511
|
* p.template // '`/pet/${petId}`'
|
|
562
512
|
*/
|
|
563
513
|
var URLPath = class {
|
|
564
|
-
/**
|
|
514
|
+
/**
|
|
515
|
+
* The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
|
|
516
|
+
*/
|
|
565
517
|
path;
|
|
566
518
|
#options;
|
|
567
519
|
constructor(path, options = {}) {
|
|
568
520
|
this.path = path;
|
|
569
521
|
this.#options = options;
|
|
570
522
|
}
|
|
571
|
-
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
523
|
+
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```ts
|
|
527
|
+
* new URLPath('/pet/{petId}').URL // '/pet/:petId'
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
572
530
|
get URL() {
|
|
573
531
|
return this.toURLPath();
|
|
574
532
|
}
|
|
575
|
-
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
533
|
+
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```ts
|
|
537
|
+
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
538
|
+
* new URLPath('/pet/{petId}').isURL // false
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
576
541
|
get isURL() {
|
|
577
542
|
try {
|
|
578
543
|
return !!new URL(this.path).href;
|
|
@@ -590,11 +555,25 @@ var URLPath = class {
|
|
|
590
555
|
get template() {
|
|
591
556
|
return this.toTemplateString();
|
|
592
557
|
}
|
|
593
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
558
|
+
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```ts
|
|
562
|
+
* new URLPath('/pet/{petId}').object
|
|
563
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
594
566
|
get object() {
|
|
595
567
|
return this.toObject();
|
|
596
568
|
}
|
|
597
|
-
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
569
|
+
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* ```ts
|
|
573
|
+
* new URLPath('/pet/{petId}').params // { petId: 'petId' }
|
|
574
|
+
* new URLPath('/pet').params // undefined
|
|
575
|
+
* ```
|
|
576
|
+
*/
|
|
598
577
|
get params() {
|
|
599
578
|
return this.getParams();
|
|
600
579
|
}
|
|
@@ -602,7 +581,9 @@ var URLPath = class {
|
|
|
602
581
|
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
603
582
|
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
604
583
|
}
|
|
605
|
-
/**
|
|
584
|
+
/**
|
|
585
|
+
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
586
|
+
*/
|
|
606
587
|
#eachParam(fn) {
|
|
607
588
|
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
608
589
|
const raw = match[1];
|
|
@@ -639,6 +620,12 @@ var URLPath = class {
|
|
|
639
620
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
640
621
|
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
641
622
|
* Returns `undefined` when no path parameters are found.
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```ts
|
|
626
|
+
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
627
|
+
* // { petId: 'petId', tagId: 'tagId' }
|
|
628
|
+
* ```
|
|
642
629
|
*/
|
|
643
630
|
getParams(replacer) {
|
|
644
631
|
const params = {};
|
|
@@ -648,7 +635,13 @@ var URLPath = class {
|
|
|
648
635
|
});
|
|
649
636
|
return Object.keys(params).length > 0 ? params : void 0;
|
|
650
637
|
}
|
|
651
|
-
/** Converts the OpenAPI path to Express-style colon syntax
|
|
638
|
+
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* ```ts
|
|
642
|
+
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
652
645
|
toURLPath() {
|
|
653
646
|
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
654
647
|
}
|
|
@@ -666,11 +659,27 @@ function isInputPath(config) {
|
|
|
666
659
|
}
|
|
667
660
|
//#endregion
|
|
668
661
|
//#region src/constants.ts
|
|
662
|
+
/**
|
|
663
|
+
* Base URL for the Kubb Studio web app.
|
|
664
|
+
*/
|
|
669
665
|
const DEFAULT_STUDIO_URL = "https://studio.kubb.dev";
|
|
666
|
+
/**
|
|
667
|
+
* File name used for generated barrel (index) files.
|
|
668
|
+
*/
|
|
670
669
|
const BARREL_FILENAME = "index.ts";
|
|
670
|
+
/**
|
|
671
|
+
* Default banner style written at the top of every generated file.
|
|
672
|
+
*/
|
|
671
673
|
const DEFAULT_BANNER = "simple";
|
|
674
|
+
/**
|
|
675
|
+
* Default file-extension mapping used when no explicit mapping is configured.
|
|
676
|
+
*/
|
|
672
677
|
const DEFAULT_EXTENSION = { ".ts": ".ts" };
|
|
673
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Numeric log-level thresholds used internally to compare verbosity.
|
|
680
|
+
*
|
|
681
|
+
* Higher numbers are more verbose.
|
|
682
|
+
*/
|
|
674
683
|
const logLevel = {
|
|
675
684
|
silent: Number.NEGATIVE_INFINITY,
|
|
676
685
|
error: 0,
|
|
@@ -679,6 +688,13 @@ const logLevel = {
|
|
|
679
688
|
verbose: 4,
|
|
680
689
|
debug: 5
|
|
681
690
|
};
|
|
691
|
+
/**
|
|
692
|
+
* CLI command descriptors for each supported linter.
|
|
693
|
+
*
|
|
694
|
+
* Each entry contains the executable `command`, an `args` factory that maps an
|
|
695
|
+
* output path to the correct argument list, and an `errorMessage` shown when
|
|
696
|
+
* the linter is not found.
|
|
697
|
+
*/
|
|
682
698
|
const linters = {
|
|
683
699
|
eslint: {
|
|
684
700
|
command: "eslint",
|
|
@@ -700,6 +716,13 @@ const linters = {
|
|
|
700
716
|
errorMessage: "Oxlint not found"
|
|
701
717
|
}
|
|
702
718
|
};
|
|
719
|
+
/**
|
|
720
|
+
* CLI command descriptors for each supported code formatter.
|
|
721
|
+
*
|
|
722
|
+
* Each entry contains the executable `command`, an `args` factory that maps an
|
|
723
|
+
* output path to the correct argument list, and an `errorMessage` shown when
|
|
724
|
+
* the formatter is not found.
|
|
725
|
+
*/
|
|
703
726
|
const formatters = {
|
|
704
727
|
prettier: {
|
|
705
728
|
command: "prettier",
|
|
@@ -899,7 +922,11 @@ function validateConcurrency(concurrency) {
|
|
|
899
922
|
//#endregion
|
|
900
923
|
//#region src/utils/executeStrategies.ts
|
|
901
924
|
/**
|
|
902
|
-
*
|
|
925
|
+
* Runs promise functions in sequence, threading each result into the next call.
|
|
926
|
+
*
|
|
927
|
+
* - Each function receives the accumulated state from the previous call.
|
|
928
|
+
* - Skips functions that return a falsy value (acts as a no-op for that step).
|
|
929
|
+
* - Returns an array of all individual results.
|
|
903
930
|
*/
|
|
904
931
|
function hookSeq(promises) {
|
|
905
932
|
return promises.filter(Boolean).reduce((promise, func) => {
|
|
@@ -912,7 +939,10 @@ function hookSeq(promises) {
|
|
|
912
939
|
}, Promise.resolve([]));
|
|
913
940
|
}
|
|
914
941
|
/**
|
|
915
|
-
*
|
|
942
|
+
* Runs promise functions in sequence and returns the first non-null result.
|
|
943
|
+
*
|
|
944
|
+
* - Stops as soon as `nullCheck` passes for a result (default: `!== null`).
|
|
945
|
+
* - Subsequent functions are skipped once a match is found.
|
|
916
946
|
*/
|
|
917
947
|
function hookFirst(promises, nullCheck = (state) => state !== null) {
|
|
918
948
|
let promise = Promise.resolve(null);
|
|
@@ -923,7 +953,10 @@ function hookFirst(promises, nullCheck = (state) => state !== null) {
|
|
|
923
953
|
return promise;
|
|
924
954
|
}
|
|
925
955
|
/**
|
|
926
|
-
* Runs
|
|
956
|
+
* Runs promise functions concurrently and returns all settled results.
|
|
957
|
+
*
|
|
958
|
+
* - Limits simultaneous executions to `concurrency` (default: unlimited).
|
|
959
|
+
* - Uses `Promise.allSettled` so individual failures do not cancel other tasks.
|
|
927
960
|
*/
|
|
928
961
|
function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
|
|
929
962
|
const limit = pLimit(concurrency);
|
|
@@ -931,29 +964,13 @@ function hookParallel(promises, concurrency = Number.POSITIVE_INFINITY) {
|
|
|
931
964
|
return Promise.allSettled(tasks);
|
|
932
965
|
}
|
|
933
966
|
//#endregion
|
|
934
|
-
//#region src/
|
|
935
|
-
var PromiseManager = class {
|
|
936
|
-
#options = {};
|
|
937
|
-
constructor(options = {}) {
|
|
938
|
-
this.#options = options;
|
|
939
|
-
}
|
|
940
|
-
run(strategy, promises, { concurrency = Number.POSITIVE_INFINITY } = {}) {
|
|
941
|
-
if (strategy === "seq") return hookSeq(promises);
|
|
942
|
-
if (strategy === "first") return hookFirst(promises, this.#options.nullCheck);
|
|
943
|
-
if (strategy === "parallel") return hookParallel(promises, concurrency);
|
|
944
|
-
throw new Error(`${strategy} not implemented`);
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
function isPromiseRejectedResult(result) {
|
|
948
|
-
return result.status === "rejected";
|
|
949
|
-
}
|
|
950
|
-
//#endregion
|
|
951
|
-
//#region src/PluginManager.ts
|
|
967
|
+
//#region src/PluginDriver.ts
|
|
952
968
|
function getMode(fileOrFolder) {
|
|
953
969
|
if (!fileOrFolder) return "split";
|
|
954
|
-
return
|
|
970
|
+
return extname(fileOrFolder) ? "single" : "split";
|
|
955
971
|
}
|
|
956
|
-
|
|
972
|
+
const hookFirstNullCheck = (state) => !!state?.result;
|
|
973
|
+
var PluginDriver = class {
|
|
957
974
|
config;
|
|
958
975
|
options;
|
|
959
976
|
/**
|
|
@@ -961,14 +978,13 @@ var PluginManager = class {
|
|
|
961
978
|
* the build pipeline after the adapter's `parse()` resolves.
|
|
962
979
|
*/
|
|
963
980
|
rootNode = void 0;
|
|
981
|
+
adapter = void 0;
|
|
964
982
|
#studioIsOpen = false;
|
|
965
983
|
#plugins = /* @__PURE__ */ new Set();
|
|
966
984
|
#usedPluginNames = {};
|
|
967
|
-
#promiseManager;
|
|
968
985
|
constructor(config, options) {
|
|
969
986
|
this.config = config;
|
|
970
987
|
this.options = options;
|
|
971
|
-
this.#promiseManager = new PromiseManager({ nullCheck: (state) => !!state?.result });
|
|
972
988
|
[...config.plugins || []].forEach((plugin) => {
|
|
973
989
|
const parsedPlugin = this.#parse(plugin);
|
|
974
990
|
this.#plugins.add(parsedPlugin);
|
|
@@ -979,14 +995,14 @@ var PluginManager = class {
|
|
|
979
995
|
}
|
|
980
996
|
getContext(plugin) {
|
|
981
997
|
const plugins = [...this.#plugins];
|
|
982
|
-
const
|
|
998
|
+
const driver = this;
|
|
983
999
|
const baseContext = {
|
|
984
1000
|
fabric: this.options.fabric,
|
|
985
1001
|
config: this.config,
|
|
986
1002
|
plugin,
|
|
987
1003
|
events: this.options.events,
|
|
988
|
-
|
|
989
|
-
mode: getMode(
|
|
1004
|
+
driver: this,
|
|
1005
|
+
mode: getMode(resolve(this.config.root, this.config.output.path)),
|
|
990
1006
|
addFile: async (...files) => {
|
|
991
1007
|
await this.options.fabric.addFile(...files);
|
|
992
1008
|
},
|
|
@@ -994,15 +1010,18 @@ var PluginManager = class {
|
|
|
994
1010
|
await this.options.fabric.upsertFile(...files);
|
|
995
1011
|
},
|
|
996
1012
|
get rootNode() {
|
|
997
|
-
return
|
|
1013
|
+
return driver.rootNode;
|
|
1014
|
+
},
|
|
1015
|
+
get adapter() {
|
|
1016
|
+
return driver.adapter;
|
|
998
1017
|
},
|
|
999
1018
|
openInStudio(options) {
|
|
1000
|
-
if (
|
|
1001
|
-
if (
|
|
1002
|
-
if (
|
|
1003
|
-
|
|
1004
|
-
const studioUrl =
|
|
1005
|
-
return openInStudio(
|
|
1019
|
+
if (!driver.config.devtools || driver.#studioIsOpen) return;
|
|
1020
|
+
if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
|
|
1021
|
+
if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
|
|
1022
|
+
driver.#studioIsOpen = true;
|
|
1023
|
+
const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
|
|
1024
|
+
return openInStudio(driver.rootNode, studioUrl, options);
|
|
1006
1025
|
}
|
|
1007
1026
|
};
|
|
1008
1027
|
const mergedExtras = {};
|
|
@@ -1019,17 +1038,21 @@ var PluginManager = class {
|
|
|
1019
1038
|
return this.#getSortedPlugins();
|
|
1020
1039
|
}
|
|
1021
1040
|
getFile({ name, mode, extname, pluginName, options }) {
|
|
1022
|
-
const
|
|
1041
|
+
const resolvedName = mode ? mode === "single" ? "" : this.resolveName({
|
|
1042
|
+
name,
|
|
1043
|
+
pluginName,
|
|
1044
|
+
type: "file"
|
|
1045
|
+
}) : name;
|
|
1023
1046
|
const path = this.resolvePath({
|
|
1024
|
-
baseName
|
|
1047
|
+
baseName: `${resolvedName}${extname}`,
|
|
1025
1048
|
mode,
|
|
1026
1049
|
pluginName,
|
|
1027
1050
|
options
|
|
1028
1051
|
});
|
|
1029
|
-
if (!path) throw new Error(`Filepath should be defined for resolvedName "${
|
|
1052
|
+
if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
|
|
1030
1053
|
return {
|
|
1031
1054
|
path,
|
|
1032
|
-
baseName,
|
|
1055
|
+
baseName: basename(path),
|
|
1033
1056
|
meta: { pluginName },
|
|
1034
1057
|
sources: [],
|
|
1035
1058
|
imports: [],
|
|
@@ -1037,8 +1060,7 @@ var PluginManager = class {
|
|
|
1037
1060
|
};
|
|
1038
1061
|
}
|
|
1039
1062
|
resolvePath = (params) => {
|
|
1040
|
-
const
|
|
1041
|
-
const defaultPath = path.resolve(root, params.baseName);
|
|
1063
|
+
const defaultPath = resolve(resolve(this.config.root, this.config.output.path), params.baseName);
|
|
1042
1064
|
if (params.pluginName) return this.hookForPluginSync({
|
|
1043
1065
|
pluginName: params.pluginName,
|
|
1044
1066
|
hookName: "resolvePath",
|
|
@@ -1118,7 +1140,7 @@ var PluginManager = class {
|
|
|
1118
1140
|
hookName,
|
|
1119
1141
|
plugins
|
|
1120
1142
|
});
|
|
1121
|
-
const
|
|
1143
|
+
const result = await hookFirst(plugins.map((plugin) => {
|
|
1122
1144
|
return async () => {
|
|
1123
1145
|
const value = await this.#execute({
|
|
1124
1146
|
strategy: "hookFirst",
|
|
@@ -1131,8 +1153,7 @@ var PluginManager = class {
|
|
|
1131
1153
|
result: value
|
|
1132
1154
|
});
|
|
1133
1155
|
};
|
|
1134
|
-
});
|
|
1135
|
-
const result = await this.#promiseManager.run("first", promises);
|
|
1156
|
+
}), hookFirstNullCheck);
|
|
1136
1157
|
this.events.emit("plugins:hook:progress:end", { hookName });
|
|
1137
1158
|
return result;
|
|
1138
1159
|
}
|
|
@@ -1168,7 +1189,7 @@ var PluginManager = class {
|
|
|
1168
1189
|
plugins
|
|
1169
1190
|
});
|
|
1170
1191
|
const pluginStartTimes = /* @__PURE__ */ new Map();
|
|
1171
|
-
const
|
|
1192
|
+
const results = await hookParallel(plugins.map((plugin) => {
|
|
1172
1193
|
return () => {
|
|
1173
1194
|
pluginStartTimes.set(plugin, performance.now());
|
|
1174
1195
|
return this.#execute({
|
|
@@ -1178,8 +1199,7 @@ var PluginManager = class {
|
|
|
1178
1199
|
plugin
|
|
1179
1200
|
});
|
|
1180
1201
|
};
|
|
1181
|
-
});
|
|
1182
|
-
const results = await this.#promiseManager.run("parallel", promises, { concurrency: this.options.concurrency });
|
|
1202
|
+
}), this.options.concurrency);
|
|
1183
1203
|
results.forEach((result, index) => {
|
|
1184
1204
|
if (isPromiseRejectedResult(result)) {
|
|
1185
1205
|
const plugin = this.#getSortedPlugins(hookName)[index];
|
|
@@ -1210,15 +1230,14 @@ var PluginManager = class {
|
|
|
1210
1230
|
hookName,
|
|
1211
1231
|
plugins
|
|
1212
1232
|
});
|
|
1213
|
-
|
|
1233
|
+
await hookSeq(plugins.map((plugin) => {
|
|
1214
1234
|
return () => this.#execute({
|
|
1215
1235
|
strategy: "hookSeq",
|
|
1216
1236
|
hookName,
|
|
1217
1237
|
parameters,
|
|
1218
1238
|
plugin
|
|
1219
1239
|
});
|
|
1220
|
-
});
|
|
1221
|
-
await this.#promiseManager.run("seq", promises);
|
|
1240
|
+
}));
|
|
1222
1241
|
this.events.emit("plugins:hook:progress:end", { hookName });
|
|
1223
1242
|
}
|
|
1224
1243
|
#getSortedPlugins(hookName) {
|
|
@@ -1226,7 +1245,8 @@ var PluginManager = class {
|
|
|
1226
1245
|
if (hookName) return plugins.filter((plugin) => hookName in plugin);
|
|
1227
1246
|
return plugins.map((plugin) => {
|
|
1228
1247
|
if (plugin.pre) {
|
|
1229
|
-
|
|
1248
|
+
let missingPlugins = plugin.pre.filter((pluginName) => !plugins.find((pluginToFind) => pluginToFind.name === pluginName));
|
|
1249
|
+
if (missingPlugins.includes("plugin-oas") && this.adapter) missingPlugins = missingPlugins.filter((pluginName) => pluginName !== "plugin-oas");
|
|
1230
1250
|
if (missingPlugins.length > 0) throw new ValidationPluginError(`The plugin '${plugin.name}' has a pre set that references missing plugins for '${missingPlugins.join(", ")}'`);
|
|
1231
1251
|
}
|
|
1232
1252
|
return plugin;
|
|
@@ -1346,19 +1366,12 @@ var PluginManager = class {
|
|
|
1346
1366
|
}
|
|
1347
1367
|
};
|
|
1348
1368
|
//#endregion
|
|
1349
|
-
//#region src/
|
|
1369
|
+
//#region src/createStorage.ts
|
|
1350
1370
|
/**
|
|
1351
|
-
*
|
|
1352
|
-
* same factory pattern as `definePlugin`, `defineLogger`, and `defineAdapter`.
|
|
1353
|
-
*
|
|
1354
|
-
* The builder receives the resolved options object and must return a
|
|
1355
|
-
* `DefineStorage`-compatible object that includes a `name` string.
|
|
1371
|
+
* Creates a storage factory. Call the returned function with optional options to get the storage instance.
|
|
1356
1372
|
*
|
|
1357
1373
|
* @example
|
|
1358
|
-
*
|
|
1359
|
-
* import { defineStorage } from '@kubb/core'
|
|
1360
|
-
*
|
|
1361
|
-
* export const memoryStorage = defineStorage((_options) => {
|
|
1374
|
+
* export const memoryStorage = createStorage(() => {
|
|
1362
1375
|
* const store = new Map<string, string>()
|
|
1363
1376
|
* return {
|
|
1364
1377
|
* name: 'memory',
|
|
@@ -1366,13 +1379,15 @@ var PluginManager = class {
|
|
|
1366
1379
|
* async getItem(key) { return store.get(key) ?? null },
|
|
1367
1380
|
* async setItem(key, value) { store.set(key, value) },
|
|
1368
1381
|
* async removeItem(key) { store.delete(key) },
|
|
1369
|
-
* async getKeys() {
|
|
1370
|
-
*
|
|
1382
|
+
* async getKeys(base) {
|
|
1383
|
+
* const keys = [...store.keys()]
|
|
1384
|
+
* return base ? keys.filter((k) => k.startsWith(base)) : keys
|
|
1385
|
+
* },
|
|
1386
|
+
* async clear(base) { if (!base) store.clear() },
|
|
1371
1387
|
* }
|
|
1372
1388
|
* })
|
|
1373
|
-
* ```
|
|
1374
1389
|
*/
|
|
1375
|
-
function
|
|
1390
|
+
function createStorage(build) {
|
|
1376
1391
|
return (options) => build(options ?? {});
|
|
1377
1392
|
}
|
|
1378
1393
|
//#endregion
|
|
@@ -1400,7 +1415,7 @@ function defineStorage(build) {
|
|
|
1400
1415
|
* })
|
|
1401
1416
|
* ```
|
|
1402
1417
|
*/
|
|
1403
|
-
const fsStorage =
|
|
1418
|
+
const fsStorage = createStorage(() => ({
|
|
1404
1419
|
name: "fs",
|
|
1405
1420
|
async hasItem(key) {
|
|
1406
1421
|
try {
|
|
@@ -1448,11 +1463,14 @@ const fsStorage = defineStorage(() => ({
|
|
|
1448
1463
|
}));
|
|
1449
1464
|
//#endregion
|
|
1450
1465
|
//#region package.json
|
|
1451
|
-
var version$1 = "5.0.0-alpha.
|
|
1466
|
+
var version$1 = "5.0.0-alpha.21";
|
|
1452
1467
|
//#endregion
|
|
1453
1468
|
//#region src/utils/diagnostics.ts
|
|
1454
1469
|
/**
|
|
1455
|
-
*
|
|
1470
|
+
* Returns a snapshot of the current runtime environment.
|
|
1471
|
+
*
|
|
1472
|
+
* Useful for attaching context to debug logs and error reports so that
|
|
1473
|
+
* issues can be reproduced without manual information gathering.
|
|
1456
1474
|
*/
|
|
1457
1475
|
function getDiagnosticInfo() {
|
|
1458
1476
|
return {
|
|
@@ -1465,6 +1483,17 @@ function getDiagnosticInfo() {
|
|
|
1465
1483
|
}
|
|
1466
1484
|
//#endregion
|
|
1467
1485
|
//#region src/build.ts
|
|
1486
|
+
/**
|
|
1487
|
+
* Initializes all Kubb infrastructure for a build without executing any plugins.
|
|
1488
|
+
*
|
|
1489
|
+
* - Validates the input path (when applicable).
|
|
1490
|
+
* - Applies config defaults (`root`, `output.*`, `devtools`).
|
|
1491
|
+
* - Creates the Fabric instance and wires storage, format, and lint hooks.
|
|
1492
|
+
* - Runs the adapter (if configured) to produce the universal `RootNode`.
|
|
1493
|
+
*
|
|
1494
|
+
* Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
|
|
1495
|
+
* via the `overrides` argument to reuse the same infrastructure across multiple runs.
|
|
1496
|
+
*/
|
|
1468
1497
|
async function setup(options) {
|
|
1469
1498
|
const { config: userConfig, events = new AsyncEventEmitter() } = options;
|
|
1470
1499
|
const sources = /* @__PURE__ */ new Map();
|
|
@@ -1562,7 +1591,7 @@ async function setup(options) {
|
|
|
1562
1591
|
` • Barrel type: ${definedConfig.output.barrelType || "none"}`
|
|
1563
1592
|
]
|
|
1564
1593
|
});
|
|
1565
|
-
const
|
|
1594
|
+
const pluginDriver = new PluginDriver(definedConfig, {
|
|
1566
1595
|
fabric,
|
|
1567
1596
|
events,
|
|
1568
1597
|
concurrency: 15
|
|
@@ -1573,25 +1602,32 @@ async function setup(options) {
|
|
|
1573
1602
|
date: /* @__PURE__ */ new Date(),
|
|
1574
1603
|
logs: [`Running adapter: ${definedConfig.adapter.name}`]
|
|
1575
1604
|
});
|
|
1576
|
-
|
|
1605
|
+
pluginDriver.adapter = definedConfig.adapter;
|
|
1606
|
+
pluginDriver.rootNode = await definedConfig.adapter.parse(source);
|
|
1577
1607
|
await events.emit("debug", {
|
|
1578
1608
|
date: /* @__PURE__ */ new Date(),
|
|
1579
1609
|
logs: [
|
|
1580
1610
|
`✓ Adapter '${definedConfig.adapter.name}' resolved RootNode`,
|
|
1581
|
-
` • Schemas: ${
|
|
1582
|
-
` • Operations: ${
|
|
1611
|
+
` • Schemas: ${pluginDriver.rootNode.schemas.length}`,
|
|
1612
|
+
` • Operations: ${pluginDriver.rootNode.operations.length}`
|
|
1583
1613
|
]
|
|
1584
1614
|
});
|
|
1585
1615
|
}
|
|
1586
1616
|
return {
|
|
1587
1617
|
events,
|
|
1588
1618
|
fabric,
|
|
1589
|
-
|
|
1619
|
+
driver: pluginDriver,
|
|
1590
1620
|
sources
|
|
1591
1621
|
};
|
|
1592
1622
|
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Runs a full Kubb build and throws on any error or plugin failure.
|
|
1625
|
+
*
|
|
1626
|
+
* Internally delegates to {@link safeBuild} and rethrows collected errors.
|
|
1627
|
+
* Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
|
|
1628
|
+
*/
|
|
1593
1629
|
async function build(options, overrides) {
|
|
1594
|
-
const { fabric, files,
|
|
1630
|
+
const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
|
|
1595
1631
|
if (error) throw error;
|
|
1596
1632
|
if (failedPlugins.size > 0) {
|
|
1597
1633
|
const errors = [...failedPlugins].map(({ error }) => error);
|
|
@@ -1601,20 +1637,30 @@ async function build(options, overrides) {
|
|
|
1601
1637
|
failedPlugins,
|
|
1602
1638
|
fabric,
|
|
1603
1639
|
files,
|
|
1604
|
-
|
|
1640
|
+
driver,
|
|
1605
1641
|
pluginTimings,
|
|
1606
1642
|
error: void 0,
|
|
1607
1643
|
sources
|
|
1608
1644
|
};
|
|
1609
1645
|
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Runs a full Kubb build and captures errors instead of throwing.
|
|
1648
|
+
*
|
|
1649
|
+
* - Installs each plugin in order, recording failures in `failedPlugins`.
|
|
1650
|
+
* - Generates the root barrel file when `output.barrelType` is set.
|
|
1651
|
+
* - Writes all files through Fabric.
|
|
1652
|
+
*
|
|
1653
|
+
* Returns a {@link BuildOutput} even on failure — inspect `error` and
|
|
1654
|
+
* `failedPlugins` to determine whether the build succeeded.
|
|
1655
|
+
*/
|
|
1610
1656
|
async function safeBuild(options, overrides) {
|
|
1611
|
-
const { fabric,
|
|
1657
|
+
const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
|
|
1612
1658
|
const failedPlugins = /* @__PURE__ */ new Set();
|
|
1613
1659
|
const pluginTimings = /* @__PURE__ */ new Map();
|
|
1614
|
-
const config =
|
|
1660
|
+
const config = driver.config;
|
|
1615
1661
|
try {
|
|
1616
|
-
for (const plugin of
|
|
1617
|
-
const context =
|
|
1662
|
+
for (const plugin of driver.plugins) {
|
|
1663
|
+
const context = driver.getContext(plugin);
|
|
1618
1664
|
const hrStart = process.hrtime();
|
|
1619
1665
|
const installer = plugin.install.bind(context);
|
|
1620
1666
|
try {
|
|
@@ -1687,7 +1733,7 @@ async function safeBuild(options, overrides) {
|
|
|
1687
1733
|
rootDir,
|
|
1688
1734
|
existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
|
|
1689
1735
|
config,
|
|
1690
|
-
|
|
1736
|
+
driver
|
|
1691
1737
|
}),
|
|
1692
1738
|
sources: [],
|
|
1693
1739
|
imports: [],
|
|
@@ -1705,7 +1751,7 @@ async function safeBuild(options, overrides) {
|
|
|
1705
1751
|
failedPlugins,
|
|
1706
1752
|
fabric,
|
|
1707
1753
|
files,
|
|
1708
|
-
|
|
1754
|
+
driver,
|
|
1709
1755
|
pluginTimings,
|
|
1710
1756
|
sources
|
|
1711
1757
|
};
|
|
@@ -1714,16 +1760,16 @@ async function safeBuild(options, overrides) {
|
|
|
1714
1760
|
failedPlugins,
|
|
1715
1761
|
fabric,
|
|
1716
1762
|
files: [],
|
|
1717
|
-
|
|
1763
|
+
driver,
|
|
1718
1764
|
pluginTimings,
|
|
1719
1765
|
error,
|
|
1720
1766
|
sources
|
|
1721
1767
|
};
|
|
1722
1768
|
}
|
|
1723
1769
|
}
|
|
1724
|
-
function buildBarrelExports({ barrelFiles, rootDir, existingExports, config,
|
|
1770
|
+
function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, driver }) {
|
|
1725
1771
|
const pluginNameMap = /* @__PURE__ */ new Map();
|
|
1726
|
-
for (const plugin of
|
|
1772
|
+
for (const plugin of driver.plugins) pluginNameMap.set(plugin.name, plugin);
|
|
1727
1773
|
return barrelFiles.flatMap((file) => {
|
|
1728
1774
|
const containsOnlyTypes = file.sources?.every((source) => source.isTypeOnly);
|
|
1729
1775
|
return (file.sources ?? []).flatMap((source) => {
|
|
@@ -1748,133 +1794,572 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, plu
|
|
|
1748
1794
|
function inputToAdapterSource(config) {
|
|
1749
1795
|
if (Array.isArray(config.input)) return {
|
|
1750
1796
|
type: "paths",
|
|
1751
|
-
paths: config.input.map((i) => resolve(config.root, i.path))
|
|
1797
|
+
paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))
|
|
1752
1798
|
};
|
|
1753
1799
|
if ("data" in config.input) return {
|
|
1754
1800
|
type: "data",
|
|
1755
1801
|
data: config.input.data
|
|
1756
1802
|
};
|
|
1803
|
+
if (new URLPath(config.input.path).isURL) return {
|
|
1804
|
+
type: "path",
|
|
1805
|
+
path: config.input.path
|
|
1806
|
+
};
|
|
1757
1807
|
return {
|
|
1758
1808
|
type: "path",
|
|
1759
1809
|
path: resolve(config.root, config.input.path)
|
|
1760
1810
|
};
|
|
1761
1811
|
}
|
|
1762
1812
|
//#endregion
|
|
1763
|
-
//#region src/
|
|
1813
|
+
//#region src/createAdapter.ts
|
|
1764
1814
|
/**
|
|
1765
|
-
*
|
|
1815
|
+
* Creates an adapter factory. Call the returned function with optional options to get the adapter instance.
|
|
1766
1816
|
*
|
|
1767
1817
|
* @example
|
|
1768
|
-
*
|
|
1769
|
-
* export const adapterOas = defineAdapter<OasAdapter>((options) => {
|
|
1770
|
-
* const { validate = true, dateType = 'string' } = options
|
|
1818
|
+
* export const myAdapter = createAdapter<MyAdapter>((options) => {
|
|
1771
1819
|
* return {
|
|
1772
|
-
* name:
|
|
1773
|
-
* options
|
|
1774
|
-
* parse(source) { ... },
|
|
1820
|
+
* name: 'my-adapter',
|
|
1821
|
+
* options,
|
|
1822
|
+
* async parse(source) { ... },
|
|
1775
1823
|
* }
|
|
1776
1824
|
* })
|
|
1777
|
-
*
|
|
1825
|
+
*
|
|
1826
|
+
* // instantiate
|
|
1827
|
+
* const adapter = myAdapter({ validate: true })
|
|
1778
1828
|
*/
|
|
1779
|
-
function
|
|
1829
|
+
function createAdapter(build) {
|
|
1780
1830
|
return (options) => build(options ?? {});
|
|
1781
1831
|
}
|
|
1782
1832
|
//#endregion
|
|
1833
|
+
//#region src/createPlugin.ts
|
|
1834
|
+
/**
|
|
1835
|
+
* Creates a plugin factory. Call the returned function with optional options to get the plugin instance.
|
|
1836
|
+
*
|
|
1837
|
+
* @example
|
|
1838
|
+
* export const myPlugin = createPlugin<MyPlugin>((options) => {
|
|
1839
|
+
* return {
|
|
1840
|
+
* name: 'my-plugin',
|
|
1841
|
+
* options,
|
|
1842
|
+
* resolvePath(baseName) { ... },
|
|
1843
|
+
* resolveName(name, type) { ... },
|
|
1844
|
+
* }
|
|
1845
|
+
* })
|
|
1846
|
+
*
|
|
1847
|
+
* // instantiate
|
|
1848
|
+
* const plugin = myPlugin({ output: { path: 'src/gen' } })
|
|
1849
|
+
*/
|
|
1850
|
+
function createPlugin(build) {
|
|
1851
|
+
return (options) => build(options ?? {});
|
|
1852
|
+
}
|
|
1853
|
+
//#endregion
|
|
1854
|
+
//#region src/defineBuilder.ts
|
|
1855
|
+
/**
|
|
1856
|
+
* Defines a builder for a plugin — a named collection of schema-building helpers that
|
|
1857
|
+
* can be exported alongside the plugin and imported by other plugins or generators.
|
|
1858
|
+
*
|
|
1859
|
+
* @example
|
|
1860
|
+
* export const builder = defineBuilder<PluginTs>(() => ({
|
|
1861
|
+
* name: 'default',
|
|
1862
|
+
* buildParamsSchema({ params, node, resolver }) {
|
|
1863
|
+
* return createSchema({ type: 'object', properties: [] })
|
|
1864
|
+
* },
|
|
1865
|
+
* buildDataSchemaNode({ node, resolver }) {
|
|
1866
|
+
* return createSchema({ type: 'object', properties: [] })
|
|
1867
|
+
* },
|
|
1868
|
+
* }))
|
|
1869
|
+
*/
|
|
1870
|
+
function defineBuilder(build) {
|
|
1871
|
+
return build();
|
|
1872
|
+
}
|
|
1873
|
+
//#endregion
|
|
1874
|
+
//#region src/defineGenerator.ts
|
|
1875
|
+
function defineGenerator(generator) {
|
|
1876
|
+
if (generator.type === "react") return {
|
|
1877
|
+
version: "2",
|
|
1878
|
+
Operations() {
|
|
1879
|
+
return null;
|
|
1880
|
+
},
|
|
1881
|
+
Operation() {
|
|
1882
|
+
return null;
|
|
1883
|
+
},
|
|
1884
|
+
Schema() {
|
|
1885
|
+
return null;
|
|
1886
|
+
},
|
|
1887
|
+
...generator
|
|
1888
|
+
};
|
|
1889
|
+
return {
|
|
1890
|
+
version: "2",
|
|
1891
|
+
async operations() {
|
|
1892
|
+
return [];
|
|
1893
|
+
},
|
|
1894
|
+
async operation() {
|
|
1895
|
+
return [];
|
|
1896
|
+
},
|
|
1897
|
+
async schema() {
|
|
1898
|
+
return [];
|
|
1899
|
+
},
|
|
1900
|
+
...generator
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
//#endregion
|
|
1783
1904
|
//#region src/defineLogger.ts
|
|
1905
|
+
/**
|
|
1906
|
+
* Wraps a logger definition into a typed {@link Logger}.
|
|
1907
|
+
*
|
|
1908
|
+
* @example
|
|
1909
|
+
* export const myLogger = defineLogger({
|
|
1910
|
+
* name: 'my-logger',
|
|
1911
|
+
* install(context, options) {
|
|
1912
|
+
* context.on('info', (message) => console.log('ℹ', message))
|
|
1913
|
+
* context.on('error', (error) => console.error('✗', error.message))
|
|
1914
|
+
* },
|
|
1915
|
+
* })
|
|
1916
|
+
*/
|
|
1784
1917
|
function defineLogger(logger) {
|
|
1785
|
-
return
|
|
1918
|
+
return logger;
|
|
1786
1919
|
}
|
|
1787
1920
|
//#endregion
|
|
1788
|
-
//#region src/
|
|
1921
|
+
//#region src/definePreset.ts
|
|
1789
1922
|
/**
|
|
1790
|
-
*
|
|
1923
|
+
* Creates a typed preset object that bundles a name, resolvers, optional
|
|
1924
|
+
* transformers, and optional generators — the building block for composable plugin presets.
|
|
1925
|
+
*
|
|
1926
|
+
* @example
|
|
1927
|
+
* import { definePreset } from '@kubb/core'
|
|
1928
|
+
* import { resolverTsLegacy } from '@kubb/plugin-ts'
|
|
1929
|
+
*
|
|
1930
|
+
* export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy] })
|
|
1931
|
+
*
|
|
1932
|
+
* @example
|
|
1933
|
+
* // With custom transformers
|
|
1934
|
+
* export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy], transformers: [myTransformer] })
|
|
1935
|
+
*
|
|
1936
|
+
* @example
|
|
1937
|
+
* // With generators
|
|
1938
|
+
* export const myPreset = definePreset('myPreset', { resolvers: [resolverTsLegacy], generators: [typeGeneratorLegacy] })
|
|
1791
1939
|
*/
|
|
1792
|
-
function
|
|
1793
|
-
return
|
|
1940
|
+
function definePreset(name, { resolvers, transformers, generators }) {
|
|
1941
|
+
return {
|
|
1942
|
+
name,
|
|
1943
|
+
resolvers,
|
|
1944
|
+
transformers,
|
|
1945
|
+
generators
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
//#endregion
|
|
1949
|
+
//#region src/definePresets.ts
|
|
1950
|
+
/**
|
|
1951
|
+
* Creates a typed presets registry object — a named collection of {@link Preset} entries.
|
|
1952
|
+
*
|
|
1953
|
+
* @example
|
|
1954
|
+
* import { definePreset, definePresets } from '@kubb/core'
|
|
1955
|
+
* import { resolverTsLegacy } from '@kubb/plugin-ts'
|
|
1956
|
+
*
|
|
1957
|
+
* export const myPresets = definePresets({
|
|
1958
|
+
* kubbV4: definePreset('kubbV4', { resolvers: [resolverTsLegacy] }),
|
|
1959
|
+
* })
|
|
1960
|
+
*/
|
|
1961
|
+
function definePresets(presets) {
|
|
1962
|
+
return presets;
|
|
1794
1963
|
}
|
|
1795
1964
|
//#endregion
|
|
1796
|
-
//#region src/
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
return
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1965
|
+
//#region src/defineResolver.ts
|
|
1966
|
+
/**
|
|
1967
|
+
* Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
|
|
1968
|
+
*/
|
|
1969
|
+
function matchesOperationPattern(node, type, pattern) {
|
|
1970
|
+
switch (type) {
|
|
1971
|
+
case "tag": return node.tags.some((tag) => !!tag.match(pattern));
|
|
1972
|
+
case "operationId": return !!node.operationId.match(pattern);
|
|
1973
|
+
case "path": return !!node.path.match(pattern);
|
|
1974
|
+
case "method": return !!node.method.toLowerCase().match(pattern);
|
|
1975
|
+
default: return false;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Checks if a schema matches a pattern for a given filter type (`schemaName`).
|
|
1980
|
+
*
|
|
1981
|
+
* Returns `null` when the filter type doesn't apply to schemas.
|
|
1982
|
+
*/
|
|
1983
|
+
function matchesSchemaPattern(node, type, pattern) {
|
|
1984
|
+
switch (type) {
|
|
1985
|
+
case "schemaName": return node.name ? !!node.name.match(pattern) : false;
|
|
1986
|
+
default: return null;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Default name resolver used by `defineResolver`.
|
|
1991
|
+
*
|
|
1992
|
+
* - `camelCase` for `function` and `file` types.
|
|
1993
|
+
* - `PascalCase` for `type`.
|
|
1994
|
+
* - `camelCase` for everything else.
|
|
1995
|
+
*/
|
|
1996
|
+
function defaultResolver(name, type) {
|
|
1997
|
+
let resolvedName = camelCase(name);
|
|
1998
|
+
if (type === "file" || type === "function") resolvedName = camelCase(name, { isFile: type === "file" });
|
|
1999
|
+
if (type === "type") resolvedName = pascalCase(name);
|
|
2000
|
+
return resolvedName;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Default option resolver — applies include/exclude filters and merges matching override options.
|
|
2004
|
+
*
|
|
2005
|
+
* Returns `null` when the node is filtered out by an `exclude` rule or not matched by any `include` rule.
|
|
2006
|
+
*
|
|
2007
|
+
* @example Include/exclude filtering
|
|
2008
|
+
* ```ts
|
|
2009
|
+
* const options = defaultResolveOptions(operationNode, {
|
|
2010
|
+
* options: { output: 'types' },
|
|
2011
|
+
* exclude: [{ type: 'tag', pattern: 'internal' }],
|
|
2012
|
+
* })
|
|
2013
|
+
* // → null when node has tag 'internal'
|
|
2014
|
+
* ```
|
|
2015
|
+
*
|
|
2016
|
+
* @example Override merging
|
|
2017
|
+
* ```ts
|
|
2018
|
+
* const options = defaultResolveOptions(operationNode, {
|
|
2019
|
+
* options: { enumType: 'asConst' },
|
|
2020
|
+
* override: [{ type: 'operationId', pattern: 'listPets', options: { enumType: 'enum' } }],
|
|
2021
|
+
* })
|
|
2022
|
+
* // → { enumType: 'enum' } when operationId matches
|
|
2023
|
+
* ```
|
|
2024
|
+
*/
|
|
2025
|
+
function defaultResolveOptions(node, { options, exclude = [], include, override = [] }) {
|
|
2026
|
+
if (isOperationNode(node)) {
|
|
2027
|
+
if (exclude.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
2028
|
+
if (include && !include.some(({ type, pattern }) => matchesOperationPattern(node, type, pattern))) return null;
|
|
2029
|
+
const overrideOptions = override.find(({ type, pattern }) => matchesOperationPattern(node, type, pattern))?.options;
|
|
2030
|
+
return {
|
|
2031
|
+
...options,
|
|
2032
|
+
...overrideOptions
|
|
1844
2033
|
};
|
|
1845
|
-
if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
|
|
1846
|
-
const matchedDependency = Object.keys(dependencies).find((dep) => dep.match(dependency));
|
|
1847
|
-
return matchedDependency ? dependencies[matchedDependency] : void 0;
|
|
1848
|
-
}
|
|
1849
|
-
async getVersion(dependency) {
|
|
1850
|
-
if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
|
|
1851
|
-
const packageJSON = await this.getPackageJSON();
|
|
1852
|
-
if (!packageJSON) return;
|
|
1853
|
-
return this.#match(packageJSON, dependency);
|
|
1854
|
-
}
|
|
1855
|
-
getVersionSync(dependency) {
|
|
1856
|
-
if (typeof dependency === "string" && PackageManager.#cache[dependency]) return PackageManager.#cache[dependency];
|
|
1857
|
-
const packageJSON = this.getPackageJSONSync();
|
|
1858
|
-
if (!packageJSON) return;
|
|
1859
|
-
return this.#match(packageJSON, dependency);
|
|
1860
|
-
}
|
|
1861
|
-
async isValid(dependency, version) {
|
|
1862
|
-
const packageVersion = await this.getVersion(dependency);
|
|
1863
|
-
if (!packageVersion) return false;
|
|
1864
|
-
if (packageVersion === version) return true;
|
|
1865
|
-
const semVer = coerce(packageVersion);
|
|
1866
|
-
if (!semVer) return false;
|
|
1867
|
-
return satisfies(semVer, version);
|
|
1868
|
-
}
|
|
1869
|
-
isValidSync(dependency, version) {
|
|
1870
|
-
const packageVersion = this.getVersionSync(dependency);
|
|
1871
|
-
if (!packageVersion) return false;
|
|
1872
|
-
if (packageVersion === version) return true;
|
|
1873
|
-
const semVer = coerce(packageVersion);
|
|
1874
|
-
if (!semVer) return false;
|
|
1875
|
-
return satisfies(semVer, version);
|
|
1876
2034
|
}
|
|
1877
|
-
|
|
2035
|
+
if (isSchemaNode(node)) {
|
|
2036
|
+
if (exclude.some(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)) return null;
|
|
2037
|
+
if (include) {
|
|
2038
|
+
const applicable = include.map(({ type, pattern }) => matchesSchemaPattern(node, type, pattern)).filter((r) => r !== null);
|
|
2039
|
+
if (applicable.length > 0 && !applicable.includes(true)) return null;
|
|
2040
|
+
}
|
|
2041
|
+
const overrideOptions = override.find(({ type, pattern }) => matchesSchemaPattern(node, type, pattern) === true)?.options;
|
|
2042
|
+
return {
|
|
2043
|
+
...options,
|
|
2044
|
+
...overrideOptions
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
return options;
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Default path resolver used by `defineResolver`.
|
|
2051
|
+
*
|
|
2052
|
+
* - Returns the output directory in `single` mode.
|
|
2053
|
+
* - Resolves into a tag- or path-based subdirectory when `group` and a `tag`/`path` value are provided.
|
|
2054
|
+
* - Falls back to a flat `output/baseName` path otherwise.
|
|
2055
|
+
*
|
|
2056
|
+
* A custom `group.name` function overrides the default subdirectory naming.
|
|
2057
|
+
* For `tag` groups the default is `${camelCase(tag)}Controller`.
|
|
2058
|
+
* For `path` groups the default is the first path segment after `/`.
|
|
2059
|
+
*
|
|
2060
|
+
* @example Flat output
|
|
2061
|
+
* ```ts
|
|
2062
|
+
* defaultResolvePath({ baseName: 'petTypes.ts' }, { root: '/src', output: { path: 'types' } })
|
|
2063
|
+
* // → '/src/types/petTypes.ts'
|
|
2064
|
+
* ```
|
|
2065
|
+
*
|
|
2066
|
+
* @example Tag-based grouping
|
|
2067
|
+
* ```ts
|
|
2068
|
+
* defaultResolvePath(
|
|
2069
|
+
* { baseName: 'petTypes.ts', tag: 'pets' },
|
|
2070
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
2071
|
+
* )
|
|
2072
|
+
* // → '/src/types/petsController/petTypes.ts'
|
|
2073
|
+
* ```
|
|
2074
|
+
*
|
|
2075
|
+
* @example Path-based grouping
|
|
2076
|
+
* ```ts
|
|
2077
|
+
* defaultResolvePath(
|
|
2078
|
+
* { baseName: 'petTypes.ts', path: '/pets/list' },
|
|
2079
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'path' } },
|
|
2080
|
+
* )
|
|
2081
|
+
* // → '/src/types/pets/petTypes.ts'
|
|
2082
|
+
* ```
|
|
2083
|
+
*
|
|
2084
|
+
* @example Single-file mode
|
|
2085
|
+
* ```ts
|
|
2086
|
+
* defaultResolvePath(
|
|
2087
|
+
* { baseName: 'petTypes.ts', pathMode: 'single' },
|
|
2088
|
+
* { root: '/src', output: { path: 'types' } },
|
|
2089
|
+
* )
|
|
2090
|
+
* // → '/src/types'
|
|
2091
|
+
* ```
|
|
2092
|
+
*/
|
|
2093
|
+
function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root, output, group }) {
|
|
2094
|
+
if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single") return path.resolve(root, output.path);
|
|
2095
|
+
if (group && (groupPath || tag)) {
|
|
2096
|
+
const groupName = group.name ? group.name : (ctx) => {
|
|
2097
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
2098
|
+
return `${camelCase(ctx.group)}Controller`;
|
|
2099
|
+
};
|
|
2100
|
+
return path.resolve(root, output.path, groupName({ group: group.type === "path" ? groupPath : tag }), baseName);
|
|
2101
|
+
}
|
|
2102
|
+
return path.resolve(root, output.path, baseName);
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Default file resolver used by `defineResolver`.
|
|
2106
|
+
*
|
|
2107
|
+
* Resolves a `KubbFile.File` by combining name resolution (`resolver.default`) with
|
|
2108
|
+
* path resolution (`resolver.resolvePath`). The resolved file always has empty
|
|
2109
|
+
* `sources`, `imports`, and `exports` arrays — consumers populate those separately.
|
|
2110
|
+
*
|
|
2111
|
+
* In `single` mode the name is omitted and the file sits directly in the output directory.
|
|
2112
|
+
*
|
|
2113
|
+
* @example Resolve a schema file
|
|
2114
|
+
* ```ts
|
|
2115
|
+
* const file = defaultResolveFile.call(resolver,
|
|
2116
|
+
* { name: 'pet', extname: '.ts' },
|
|
2117
|
+
* { root: '/src', output: { path: 'types' } },
|
|
2118
|
+
* )
|
|
2119
|
+
* // → { baseName: 'pet.ts', path: '/src/types/pet.ts', sources: [], ... }
|
|
2120
|
+
* ```
|
|
2121
|
+
*
|
|
2122
|
+
* @example Resolve an operation file with tag grouping
|
|
2123
|
+
* ```ts
|
|
2124
|
+
* const file = defaultResolveFile.call(resolver,
|
|
2125
|
+
* { name: 'listPets', extname: '.ts', tag: 'pets' },
|
|
2126
|
+
* { root: '/src', output: { path: 'types' }, group: { type: 'tag' } },
|
|
2127
|
+
* )
|
|
2128
|
+
* // → { baseName: 'listPets.ts', path: '/src/types/petsController/listPets.ts', ... }
|
|
2129
|
+
* ```
|
|
2130
|
+
*/
|
|
2131
|
+
function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
|
|
2132
|
+
const pathMode = getMode(path.resolve(context.root, context.output.path));
|
|
2133
|
+
const baseName = `${pathMode === "single" ? "" : this.default(name, "file")}${extname}`;
|
|
2134
|
+
const filePath = this.resolvePath({
|
|
2135
|
+
baseName,
|
|
2136
|
+
pathMode,
|
|
2137
|
+
tag,
|
|
2138
|
+
path: groupPath
|
|
2139
|
+
}, context);
|
|
2140
|
+
return {
|
|
2141
|
+
path: filePath,
|
|
2142
|
+
baseName: path.basename(filePath),
|
|
2143
|
+
meta: { pluginName: this.pluginName },
|
|
2144
|
+
sources: [],
|
|
2145
|
+
imports: [],
|
|
2146
|
+
exports: []
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Generates the default "Generated by Kubb" banner from config and optional node metadata.
|
|
2151
|
+
*/
|
|
2152
|
+
function buildDefaultBanner({ title, description, version, config }) {
|
|
2153
|
+
try {
|
|
2154
|
+
let source = "";
|
|
2155
|
+
if (Array.isArray(config.input)) {
|
|
2156
|
+
const first = config.input[0];
|
|
2157
|
+
if (first && "path" in first) source = path.basename(first.path);
|
|
2158
|
+
} else if ("path" in config.input) source = path.basename(config.input.path);
|
|
2159
|
+
else if ("data" in config.input) source = "text content";
|
|
2160
|
+
let banner = "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n";
|
|
2161
|
+
if (config.output.defaultBanner === "simple") {
|
|
2162
|
+
banner += "*/\n";
|
|
2163
|
+
return banner;
|
|
2164
|
+
}
|
|
2165
|
+
if (source) banner += `* Source: ${source}\n`;
|
|
2166
|
+
if (title) banner += `* Title: ${title}\n`;
|
|
2167
|
+
if (description) {
|
|
2168
|
+
const formattedDescription = description.replace(/\n/gm, "\n* ");
|
|
2169
|
+
banner += `* Description: ${formattedDescription}\n`;
|
|
2170
|
+
}
|
|
2171
|
+
if (version) banner += `* OpenAPI spec version: ${version}\n`;
|
|
2172
|
+
banner += "*/\n";
|
|
2173
|
+
return banner;
|
|
2174
|
+
} catch (_error) {
|
|
2175
|
+
return "/**\n* Generated by Kubb (https://kubb.dev/).\n* Do not edit manually.\n*/";
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Default banner resolver — returns the banner string for a generated file.
|
|
2180
|
+
*
|
|
2181
|
+
* - When `output.banner` is a function and `node` is provided, calls it with the node.
|
|
2182
|
+
* - When `output.banner` is a function and `node` is absent, falls back to the default Kubb banner.
|
|
2183
|
+
* - When `output.banner` is a string, returns it directly.
|
|
2184
|
+
* - When `config.output.defaultBanner` is `false`, returns `undefined`.
|
|
2185
|
+
* - Otherwise returns the default "Generated by Kubb" banner.
|
|
2186
|
+
*
|
|
2187
|
+
* @example String banner
|
|
2188
|
+
* ```ts
|
|
2189
|
+
* defaultResolveBanner(undefined, { output: { banner: '// my banner' }, config })
|
|
2190
|
+
* // → '// my banner'
|
|
2191
|
+
* ```
|
|
2192
|
+
*
|
|
2193
|
+
* @example Function banner with node
|
|
2194
|
+
* ```ts
|
|
2195
|
+
* defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
|
|
2196
|
+
* // → '// v3.0.0'
|
|
2197
|
+
* ```
|
|
2198
|
+
*
|
|
2199
|
+
* @example Disabled banner
|
|
2200
|
+
* ```ts
|
|
2201
|
+
* defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
|
|
2202
|
+
* // → undefined
|
|
2203
|
+
* ```
|
|
2204
|
+
*/
|
|
2205
|
+
function defaultResolveBanner(node, { output, config }) {
|
|
2206
|
+
if (typeof output?.banner === "function") return node ? output.banner(node) : buildDefaultBanner({ config });
|
|
2207
|
+
if (typeof output?.banner === "string") return output.banner;
|
|
2208
|
+
if (config.output.defaultBanner === false) return;
|
|
2209
|
+
return buildDefaultBanner({ config });
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Default footer resolver — returns the footer string for a generated file.
|
|
2213
|
+
*
|
|
2214
|
+
* - When `output.footer` is a function and `node` is provided, calls it with the node.
|
|
2215
|
+
* - When `output.footer` is a function and `node` is absent, returns `undefined`.
|
|
2216
|
+
* - When `output.footer` is a string, returns it directly.
|
|
2217
|
+
* - Otherwise returns `undefined`.
|
|
2218
|
+
*
|
|
2219
|
+
* @example String footer
|
|
2220
|
+
* ```ts
|
|
2221
|
+
* defaultResolveFooter(undefined, { output: { footer: '// end of file' }, config })
|
|
2222
|
+
* // → '// end of file'
|
|
2223
|
+
* ```
|
|
2224
|
+
*
|
|
2225
|
+
* @example Function footer with node
|
|
2226
|
+
* ```ts
|
|
2227
|
+
* defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
|
|
2228
|
+
* // → '// Pet Store'
|
|
2229
|
+
* ```
|
|
2230
|
+
*/
|
|
2231
|
+
function defaultResolveFooter(node, { output }) {
|
|
2232
|
+
if (typeof output?.footer === "function") return node ? output.footer(node) : void 0;
|
|
2233
|
+
if (typeof output?.footer === "string") return output.footer;
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Defines a resolver for a plugin, injecting built-in defaults for name casing,
|
|
2237
|
+
* include/exclude/override filtering, path resolution, and file construction.
|
|
2238
|
+
*
|
|
2239
|
+
* All four defaults can be overridden by providing them in the builder function:
|
|
2240
|
+
* - `default` — name casing strategy (camelCase / PascalCase)
|
|
2241
|
+
* - `resolveOptions` — include/exclude/override filtering
|
|
2242
|
+
* - `resolvePath` — output path computation
|
|
2243
|
+
* - `resolveFile` — full `KubbFile.File` construction
|
|
2244
|
+
*
|
|
2245
|
+
* Methods in the builder have access to `this` (the full resolver object), so they
|
|
2246
|
+
* can call other resolver methods without circular imports.
|
|
2247
|
+
*
|
|
2248
|
+
* @example Basic resolver with naming helpers
|
|
2249
|
+
* ```ts
|
|
2250
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
2251
|
+
* name: 'default',
|
|
2252
|
+
* resolveName(node) {
|
|
2253
|
+
* return this.default(node.name, 'function')
|
|
2254
|
+
* },
|
|
2255
|
+
* resolveTypedName(node) {
|
|
2256
|
+
* return this.default(node.name, 'type')
|
|
2257
|
+
* },
|
|
2258
|
+
* }))
|
|
2259
|
+
* ```
|
|
2260
|
+
*
|
|
2261
|
+
* @example Override resolvePath for a custom output structure
|
|
2262
|
+
* ```ts
|
|
2263
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
2264
|
+
* name: 'custom',
|
|
2265
|
+
* resolvePath({ baseName }, { root, output }) {
|
|
2266
|
+
* return path.resolve(root, output.path, 'generated', baseName)
|
|
2267
|
+
* },
|
|
2268
|
+
* }))
|
|
2269
|
+
* ```
|
|
2270
|
+
*
|
|
2271
|
+
* @example Use this.default inside a helper
|
|
2272
|
+
* ```ts
|
|
2273
|
+
* export const resolver = defineResolver<PluginTs>(() => ({
|
|
2274
|
+
* name: 'default',
|
|
2275
|
+
* resolveParamName(node, param) {
|
|
2276
|
+
* return this.default(`${node.operationId} ${param.in} ${param.name}`, 'type')
|
|
2277
|
+
* },
|
|
2278
|
+
* }))
|
|
2279
|
+
* ```
|
|
2280
|
+
*/
|
|
2281
|
+
function defineResolver(build) {
|
|
2282
|
+
return {
|
|
2283
|
+
default: defaultResolver,
|
|
2284
|
+
resolveOptions: defaultResolveOptions,
|
|
2285
|
+
resolvePath: defaultResolvePath,
|
|
2286
|
+
resolveFile: defaultResolveFile,
|
|
2287
|
+
resolveBanner: defaultResolveBanner,
|
|
2288
|
+
resolveFooter: defaultResolveFooter,
|
|
2289
|
+
...build()
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
//#endregion
|
|
2293
|
+
//#region src/renderNode.tsx
|
|
2294
|
+
/**
|
|
2295
|
+
* Renders a React component for a list of operation nodes (V2 generators).
|
|
2296
|
+
*/
|
|
2297
|
+
async function renderOperations(nodes, options) {
|
|
2298
|
+
const { config, fabric, plugin, Component, driver, adapter } = options;
|
|
2299
|
+
if (!Component) return;
|
|
2300
|
+
const fabricChild = createReactFabric();
|
|
2301
|
+
await fabricChild.render(/* @__PURE__ */ jsx(Fabric, {
|
|
2302
|
+
meta: {
|
|
2303
|
+
plugin,
|
|
2304
|
+
driver
|
|
2305
|
+
},
|
|
2306
|
+
children: /* @__PURE__ */ jsx(Component, {
|
|
2307
|
+
config,
|
|
2308
|
+
plugin,
|
|
2309
|
+
adapter,
|
|
2310
|
+
nodes,
|
|
2311
|
+
options: options.options
|
|
2312
|
+
})
|
|
2313
|
+
}));
|
|
2314
|
+
fabric.context.fileManager.upsert(...fabricChild.files);
|
|
2315
|
+
fabricChild.unmount();
|
|
2316
|
+
}
|
|
2317
|
+
/**
|
|
2318
|
+
* Renders a React component for a single operation node (V2 generators).
|
|
2319
|
+
*/
|
|
2320
|
+
async function renderOperation(node, options) {
|
|
2321
|
+
const { config, fabric, plugin, Component, adapter, driver } = options;
|
|
2322
|
+
if (!Component) return;
|
|
2323
|
+
const fabricChild = createReactFabric();
|
|
2324
|
+
await fabricChild.render(/* @__PURE__ */ jsx(Fabric, {
|
|
2325
|
+
meta: {
|
|
2326
|
+
plugin,
|
|
2327
|
+
driver
|
|
2328
|
+
},
|
|
2329
|
+
children: /* @__PURE__ */ jsx(Component, {
|
|
2330
|
+
config,
|
|
2331
|
+
plugin,
|
|
2332
|
+
adapter,
|
|
2333
|
+
node,
|
|
2334
|
+
options: options.options
|
|
2335
|
+
})
|
|
2336
|
+
}));
|
|
2337
|
+
fabric.context.fileManager.upsert(...fabricChild.files);
|
|
2338
|
+
fabricChild.unmount();
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Renders a React component for a single schema node (V2 generators).
|
|
2342
|
+
*/
|
|
2343
|
+
async function renderSchema(node, options) {
|
|
2344
|
+
const { config, fabric, plugin, Component, adapter, driver } = options;
|
|
2345
|
+
if (!Component) return;
|
|
2346
|
+
const fabricChild = createReactFabric();
|
|
2347
|
+
await fabricChild.render(/* @__PURE__ */ jsx(Fabric, {
|
|
2348
|
+
meta: {
|
|
2349
|
+
plugin,
|
|
2350
|
+
driver
|
|
2351
|
+
},
|
|
2352
|
+
children: /* @__PURE__ */ jsx(Component, {
|
|
2353
|
+
config,
|
|
2354
|
+
plugin,
|
|
2355
|
+
adapter,
|
|
2356
|
+
node,
|
|
2357
|
+
options: options.options
|
|
2358
|
+
})
|
|
2359
|
+
}));
|
|
2360
|
+
fabric.context.fileManager.upsert(...fabricChild.files);
|
|
2361
|
+
fabricChild.unmount();
|
|
2362
|
+
}
|
|
1878
2363
|
//#endregion
|
|
1879
2364
|
//#region src/storages/memoryStorage.ts
|
|
1880
2365
|
/**
|
|
@@ -1894,7 +2379,7 @@ var PackageManager = class PackageManager {
|
|
|
1894
2379
|
* })
|
|
1895
2380
|
* ```
|
|
1896
2381
|
*/
|
|
1897
|
-
const memoryStorage =
|
|
2382
|
+
const memoryStorage = createStorage(() => {
|
|
1898
2383
|
const store = /* @__PURE__ */ new Map();
|
|
1899
2384
|
return {
|
|
1900
2385
|
name: "memory",
|
|
@@ -1926,7 +2411,7 @@ const memoryStorage = defineStorage(() => {
|
|
|
1926
2411
|
//#endregion
|
|
1927
2412
|
//#region src/utils/FunctionParams.ts
|
|
1928
2413
|
/**
|
|
1929
|
-
* @deprecated
|
|
2414
|
+
* @deprecated use ast package instead
|
|
1930
2415
|
*/
|
|
1931
2416
|
var FunctionParams = class FunctionParams {
|
|
1932
2417
|
#items = [];
|
|
@@ -2002,14 +2487,10 @@ var FunctionParams = class FunctionParams {
|
|
|
2002
2487
|
//#endregion
|
|
2003
2488
|
//#region src/utils/formatters.ts
|
|
2004
2489
|
/**
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2007
|
-
* @param formatter - The formatter to check ('biome', 'prettier', or 'oxfmt')
|
|
2008
|
-
* @returns Promise that resolves to true if the formatter is available, false otherwise
|
|
2490
|
+
* Returns `true` when the given formatter is installed and callable.
|
|
2009
2491
|
*
|
|
2010
|
-
*
|
|
2011
|
-
*
|
|
2012
|
-
* All supported formatters (biome, prettier, oxfmt) implement the --version flag.
|
|
2492
|
+
* Availability is detected by running `<formatter> --version` and checking
|
|
2493
|
+
* that the process exits without error.
|
|
2013
2494
|
*/
|
|
2014
2495
|
async function isFormatterAvailable(formatter) {
|
|
2015
2496
|
try {
|
|
@@ -2020,34 +2501,39 @@ async function isFormatterAvailable(formatter) {
|
|
|
2020
2501
|
}
|
|
2021
2502
|
}
|
|
2022
2503
|
/**
|
|
2023
|
-
*
|
|
2504
|
+
* Detects the first available code formatter on the current system.
|
|
2024
2505
|
*
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
2027
|
-
* @remarks
|
|
2028
|
-
* Checks in order of preference: biome, oxfmt, prettier.
|
|
2029
|
-
* Uses the `--version` flag to detect if each formatter command is available.
|
|
2030
|
-
* This is a reliable method as all supported formatters implement this flag.
|
|
2506
|
+
* - Checks in preference order: `biome`, `oxfmt`, `prettier`.
|
|
2507
|
+
* - Returns `null` when none are found.
|
|
2031
2508
|
*
|
|
2032
2509
|
* @example
|
|
2033
|
-
* ```
|
|
2510
|
+
* ```ts
|
|
2034
2511
|
* const formatter = await detectFormatter()
|
|
2035
2512
|
* if (formatter) {
|
|
2036
2513
|
* console.log(`Using ${formatter} for formatting`)
|
|
2037
|
-
* } else {
|
|
2038
|
-
* console.log('No formatter found')
|
|
2039
2514
|
* }
|
|
2040
2515
|
* ```
|
|
2041
2516
|
*/
|
|
2042
2517
|
async function detectFormatter() {
|
|
2043
|
-
|
|
2518
|
+
const formatterNames = new Set([
|
|
2044
2519
|
"biome",
|
|
2045
2520
|
"oxfmt",
|
|
2046
2521
|
"prettier"
|
|
2047
|
-
])
|
|
2522
|
+
]);
|
|
2523
|
+
for (const formatter of formatterNames) if (await isFormatterAvailable(formatter)) return formatter;
|
|
2524
|
+
return null;
|
|
2048
2525
|
}
|
|
2049
2526
|
//#endregion
|
|
2050
2527
|
//#region src/utils/TreeNode.ts
|
|
2528
|
+
/**
|
|
2529
|
+
* Tree structure used to build per-directory barrel (`index.ts`) files from a
|
|
2530
|
+
* flat list of generated {@link KubbFile.File} entries.
|
|
2531
|
+
*
|
|
2532
|
+
* Each node represents either a directory or a file within the output tree.
|
|
2533
|
+
* Use {@link TreeNode.build} to construct a root node from a file list, then
|
|
2534
|
+
* traverse with {@link TreeNode.forEach}, {@link TreeNode.leaves}, or the
|
|
2535
|
+
* `*Deep` helpers.
|
|
2536
|
+
*/
|
|
2051
2537
|
var TreeNode = class TreeNode {
|
|
2052
2538
|
data;
|
|
2053
2539
|
parent;
|
|
@@ -2063,10 +2549,18 @@ var TreeNode = class TreeNode {
|
|
|
2063
2549
|
this.children.push(child);
|
|
2064
2550
|
return child;
|
|
2065
2551
|
}
|
|
2552
|
+
/**
|
|
2553
|
+
* Returns the root ancestor of this node, walking up via `parent` links.
|
|
2554
|
+
*/
|
|
2066
2555
|
get root() {
|
|
2067
2556
|
if (!this.parent) return this;
|
|
2068
2557
|
return this.parent.root;
|
|
2069
2558
|
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Returns all leaf descendants (nodes with no children) of this node.
|
|
2561
|
+
*
|
|
2562
|
+
* Results are cached after the first traversal.
|
|
2563
|
+
*/
|
|
2070
2564
|
get leaves() {
|
|
2071
2565
|
if (!this.children || this.children.length === 0) return [this];
|
|
2072
2566
|
if (this.#cachedLeaves) return this.#cachedLeaves;
|
|
@@ -2097,6 +2591,12 @@ var TreeNode = class TreeNode {
|
|
|
2097
2591
|
if (typeof callback !== "function") throw new TypeError("map() callback must be a function");
|
|
2098
2592
|
return this.leaves.map(callback);
|
|
2099
2593
|
}
|
|
2594
|
+
/**
|
|
2595
|
+
* Builds a {@link TreeNode} tree from a flat list of files.
|
|
2596
|
+
*
|
|
2597
|
+
* - Filters to files under `root` (when provided) and skips `.json` files.
|
|
2598
|
+
* - Returns `null` when no files match.
|
|
2599
|
+
*/
|
|
2100
2600
|
static build(files, root) {
|
|
2101
2601
|
try {
|
|
2102
2602
|
const filteredTree = buildDirectoryTree(files, root);
|
|
@@ -2167,65 +2667,64 @@ function buildDirectoryTree(files, rootFolder = "") {
|
|
|
2167
2667
|
return root;
|
|
2168
2668
|
}
|
|
2169
2669
|
//#endregion
|
|
2170
|
-
//#region src/
|
|
2670
|
+
//#region src/utils/getBarrelFiles.ts
|
|
2171
2671
|
/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: not needed */
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
(item.data.file?.
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
isIndexable: false
|
|
2201
|
-
});
|
|
2672
|
+
function getBarrelFilesByRoot(root, files) {
|
|
2673
|
+
const cachedFiles = /* @__PURE__ */ new Map();
|
|
2674
|
+
TreeNode.build(files, root)?.forEach((treeNode) => {
|
|
2675
|
+
if (!treeNode?.children || !treeNode.parent?.data.path) return;
|
|
2676
|
+
const barrelFile = {
|
|
2677
|
+
path: join(treeNode.parent?.data.path, "index.ts"),
|
|
2678
|
+
baseName: "index.ts",
|
|
2679
|
+
exports: [],
|
|
2680
|
+
imports: [],
|
|
2681
|
+
sources: []
|
|
2682
|
+
};
|
|
2683
|
+
const previousBarrelFile = cachedFiles.get(barrelFile.path);
|
|
2684
|
+
treeNode.leaves.forEach((item) => {
|
|
2685
|
+
if (!item.data.name) return;
|
|
2686
|
+
(item.data.file?.sources || []).forEach((source) => {
|
|
2687
|
+
if (!item.data.file?.path || !source.isIndexable || !source.name) return;
|
|
2688
|
+
if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
|
|
2689
|
+
barrelFile.exports.push({
|
|
2690
|
+
name: [source.name],
|
|
2691
|
+
path: getRelativePath(treeNode.parent?.data.path, item.data.path),
|
|
2692
|
+
isTypeOnly: source.isTypeOnly
|
|
2693
|
+
});
|
|
2694
|
+
barrelFile.sources.push({
|
|
2695
|
+
name: source.name,
|
|
2696
|
+
isTypeOnly: source.isTypeOnly,
|
|
2697
|
+
value: "",
|
|
2698
|
+
isExportable: false,
|
|
2699
|
+
isIndexable: false
|
|
2202
2700
|
});
|
|
2203
2701
|
});
|
|
2204
|
-
if (previousBarrelFile) {
|
|
2205
|
-
previousBarrelFile.sources.push(...barrelFile.sources);
|
|
2206
|
-
previousBarrelFile.exports?.push(...barrelFile.exports || []);
|
|
2207
|
-
} else cachedFiles.set(barrelFile.path, barrelFile);
|
|
2208
2702
|
});
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2703
|
+
if (previousBarrelFile) {
|
|
2704
|
+
previousBarrelFile.sources.push(...barrelFile.sources);
|
|
2705
|
+
previousBarrelFile.exports?.push(...barrelFile.exports || []);
|
|
2706
|
+
} else cachedFiles.set(barrelFile.path, barrelFile);
|
|
2707
|
+
});
|
|
2708
|
+
return [...cachedFiles.values()];
|
|
2709
|
+
}
|
|
2214
2710
|
function trimExtName(text) {
|
|
2215
2711
|
const dotIndex = text.lastIndexOf(".");
|
|
2216
2712
|
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
2217
2713
|
return text;
|
|
2218
2714
|
}
|
|
2715
|
+
/**
|
|
2716
|
+
* Generates `index.ts` barrel files for all directories under `root/output.path`.
|
|
2717
|
+
*
|
|
2718
|
+
* - Returns an empty array when `type` is falsy or `'propagate'`.
|
|
2719
|
+
* - Skips generation when the output path itself ends with `index` (already a barrel).
|
|
2720
|
+
* - When `type` is `'all'`, strips named exports so every re-export becomes a wildcard (`export * from`).
|
|
2721
|
+
* - Attaches `meta` to each barrel file for downstream plugin identification.
|
|
2722
|
+
*/
|
|
2219
2723
|
async function getBarrelFiles(files, { type, meta = {}, root, output }) {
|
|
2220
2724
|
if (!type || type === "propagate") return [];
|
|
2221
|
-
const barrelManager = new BarrelManager();
|
|
2222
2725
|
const pathToBuildFrom = join(root, output.path);
|
|
2223
2726
|
if (trimExtName(pathToBuildFrom).endsWith("index")) return [];
|
|
2224
|
-
const barrelFiles =
|
|
2225
|
-
files,
|
|
2226
|
-
root: pathToBuildFrom,
|
|
2227
|
-
meta
|
|
2228
|
-
});
|
|
2727
|
+
const barrelFiles = getBarrelFilesByRoot(pathToBuildFrom, files);
|
|
2229
2728
|
if (type === "all") return barrelFiles.map((file) => {
|
|
2230
2729
|
return {
|
|
2231
2730
|
...file,
|
|
@@ -2245,38 +2744,62 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
|
|
|
2245
2744
|
});
|
|
2246
2745
|
}
|
|
2247
2746
|
//#endregion
|
|
2248
|
-
//#region src/utils/
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2747
|
+
//#region src/utils/getConfigs.ts
|
|
2748
|
+
/**
|
|
2749
|
+
* Resolves a {@link ConfigInput} into a normalized array of {@link Config} objects.
|
|
2750
|
+
*
|
|
2751
|
+
* - Awaits the config when it is a `Promise`.
|
|
2752
|
+
* - Calls the factory function with `args` when the config is a function.
|
|
2753
|
+
* - Wraps a single config object in an array for uniform downstream handling.
|
|
2754
|
+
*/
|
|
2755
|
+
async function getConfigs(config, args) {
|
|
2756
|
+
const resolved = await (typeof config === "function" ? config(args) : config);
|
|
2757
|
+
return (Array.isArray(resolved) ? resolved : [resolved]).map((item) => ({ ...item }));
|
|
2254
2758
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2759
|
+
//#endregion
|
|
2760
|
+
//#region src/utils/mergeResolvers.ts
|
|
2761
|
+
/**
|
|
2762
|
+
* Merges an array of resolvers into a single resolver. Later entries override earlier ones (last wins).
|
|
2763
|
+
*/
|
|
2764
|
+
function mergeResolvers(...resolvers) {
|
|
2765
|
+
return resolvers.reduce((acc, curr) => ({
|
|
2766
|
+
...acc,
|
|
2767
|
+
...curr
|
|
2768
|
+
}), resolvers[0]);
|
|
2259
2769
|
}
|
|
2260
2770
|
//#endregion
|
|
2261
|
-
//#region src/utils/
|
|
2771
|
+
//#region src/utils/getPreset.ts
|
|
2262
2772
|
/**
|
|
2263
|
-
*
|
|
2773
|
+
* Resolves a named preset into merged resolvers, transformers, and generators.
|
|
2774
|
+
*
|
|
2775
|
+
* - Merges the preset's resolvers on top of the first (default)
|
|
2776
|
+
* - Merges any additional user-supplied resolvers on top of that to produce the final `resolver`.
|
|
2777
|
+
* - Concatenates preset transformers before user-supplied transformers.
|
|
2778
|
+
* - Combines preset generators with user-supplied generators; falls back to the `default` preset's generators when neither provides any.
|
|
2264
2779
|
*/
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2780
|
+
function getPreset(params) {
|
|
2781
|
+
const { preset: presetName, presets, resolvers, transformers: userTransformers, generators: userGenerators } = params;
|
|
2782
|
+
const [defaultResolver, ...userResolvers] = resolvers;
|
|
2783
|
+
const preset = presets[presetName];
|
|
2784
|
+
const resolver = mergeResolvers(mergeResolvers(defaultResolver, ...preset?.resolvers ?? []), ...userResolvers ?? []);
|
|
2785
|
+
const transformers = [...preset?.transformers ?? [], ...userTransformers ?? []];
|
|
2786
|
+
const presetGenerators = preset?.generators ?? [];
|
|
2787
|
+
const defaultPresetGenerators = presets["default"]?.generators ?? [];
|
|
2788
|
+
return {
|
|
2789
|
+
resolver,
|
|
2790
|
+
transformers,
|
|
2791
|
+
generators: presetGenerators.length > 0 || userGenerators.length ? [...presetGenerators, ...userGenerators] : defaultPresetGenerators,
|
|
2792
|
+
preset
|
|
2793
|
+
};
|
|
2277
2794
|
}
|
|
2278
2795
|
//#endregion
|
|
2279
2796
|
//#region src/utils/linters.ts
|
|
2797
|
+
/**
|
|
2798
|
+
* Returns `true` when the given linter is installed and callable.
|
|
2799
|
+
*
|
|
2800
|
+
* Availability is detected by running `<linter> --version` and checking
|
|
2801
|
+
* that the process exits without error.
|
|
2802
|
+
*/
|
|
2280
2803
|
async function isLinterAvailable(linter) {
|
|
2281
2804
|
try {
|
|
2282
2805
|
await x(linter, ["--version"], { nodeOptions: { stdio: "ignore" } });
|
|
@@ -2285,14 +2808,73 @@ async function isLinterAvailable(linter) {
|
|
|
2285
2808
|
return false;
|
|
2286
2809
|
}
|
|
2287
2810
|
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Detects the first available linter on the current system.
|
|
2813
|
+
*
|
|
2814
|
+
* - Checks in preference order: `biome`, `oxlint`, `eslint`.
|
|
2815
|
+
* - Returns `null` when none are found.
|
|
2816
|
+
*
|
|
2817
|
+
* @example
|
|
2818
|
+
* ```ts
|
|
2819
|
+
* const linter = await detectLinter()
|
|
2820
|
+
* if (linter) {
|
|
2821
|
+
* console.log(`Using ${linter} for linting`)
|
|
2822
|
+
* }
|
|
2823
|
+
* ```
|
|
2824
|
+
*/
|
|
2288
2825
|
async function detectLinter() {
|
|
2289
|
-
|
|
2826
|
+
const linterNames = new Set([
|
|
2290
2827
|
"biome",
|
|
2291
2828
|
"oxlint",
|
|
2292
2829
|
"eslint"
|
|
2293
|
-
])
|
|
2830
|
+
]);
|
|
2831
|
+
for (const linter of linterNames) if (await isLinterAvailable(linter)) return linter;
|
|
2832
|
+
return null;
|
|
2833
|
+
}
|
|
2834
|
+
//#endregion
|
|
2835
|
+
//#region src/utils/packageJSON.ts
|
|
2836
|
+
function getPackageJSONSync(cwd) {
|
|
2837
|
+
const pkgPath = pkg.up({ cwd });
|
|
2838
|
+
if (!pkgPath) return null;
|
|
2839
|
+
return JSON.parse(readSync(pkgPath));
|
|
2840
|
+
}
|
|
2841
|
+
function match(packageJSON, dependency) {
|
|
2842
|
+
const dependencies = {
|
|
2843
|
+
...packageJSON.dependencies || {},
|
|
2844
|
+
...packageJSON.devDependencies || {}
|
|
2845
|
+
};
|
|
2846
|
+
if (typeof dependency === "string" && dependencies[dependency]) return dependencies[dependency];
|
|
2847
|
+
const matched = Object.keys(dependencies).find((dep) => dep.match(dependency));
|
|
2848
|
+
return matched ? dependencies[matched] ?? null : null;
|
|
2849
|
+
}
|
|
2850
|
+
function getVersionSync(dependency, cwd) {
|
|
2851
|
+
const packageJSON = getPackageJSONSync(cwd);
|
|
2852
|
+
return packageJSON ? match(packageJSON, dependency) : null;
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Returns `true` when the nearest `package.json` declares a dependency that
|
|
2856
|
+
* satisfies the given semver range.
|
|
2857
|
+
*
|
|
2858
|
+
* - Searches both `dependencies` and `devDependencies`.
|
|
2859
|
+
* - Accepts a string package name or a `RegExp` to match scoped/pattern packages.
|
|
2860
|
+
* - Uses `semver.satisfies` for range comparison; returns `false` when the
|
|
2861
|
+
* version string cannot be coerced into a valid semver.
|
|
2862
|
+
*
|
|
2863
|
+
* @example
|
|
2864
|
+
* ```ts
|
|
2865
|
+
* satisfiesDependency('react', '>=18') // true when react@18.x is installed
|
|
2866
|
+
* satisfiesDependency(/^@tanstack\//, '>=5') // true when any @tanstack/* >=5 is found
|
|
2867
|
+
* ```
|
|
2868
|
+
*/
|
|
2869
|
+
function satisfiesDependency(dependency, version, cwd) {
|
|
2870
|
+
const packageVersion = getVersionSync(dependency, cwd);
|
|
2871
|
+
if (!packageVersion) return false;
|
|
2872
|
+
if (packageVersion === version) return true;
|
|
2873
|
+
const semVer = coerce(packageVersion);
|
|
2874
|
+
if (!semVer) return false;
|
|
2875
|
+
return satisfies(semVer, version);
|
|
2294
2876
|
}
|
|
2295
2877
|
//#endregion
|
|
2296
|
-
export { AsyncEventEmitter, FunctionParams,
|
|
2878
|
+
export { AsyncEventEmitter, FunctionParams, PluginDriver, URLPath, build, build as default, buildDefaultBanner, createAdapter, createPlugin, createStorage, defaultResolveBanner, defaultResolveFile, defaultResolveFooter, defaultResolveOptions, defaultResolvePath, defineBuilder, defineConfig, defineGenerator, defineLogger, definePreset, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeResolvers, renderOperation, renderOperations, renderSchema, safeBuild, satisfiesDependency, setup };
|
|
2297
2879
|
|
|
2298
2880
|
//# sourceMappingURL=index.js.map
|