@kubb/ast 5.0.0-alpha.16 → 5.0.0-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1051 -696
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +229 -68
- package/dist/index.js +1038 -691
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/visitor-BFn3X90U.d.ts +1974 -0
- package/package.json +1 -1
- package/src/constants.ts +88 -4
- package/src/factory.ts +174 -37
- package/src/guards.ts +37 -10
- package/src/index.ts +6 -5
- package/src/infer.ts +130 -0
- package/src/mocks.ts +4 -3
- package/src/nodes/base.ts +21 -3
- package/src/nodes/function.ts +34 -22
- package/src/nodes/http.ts +17 -5
- package/src/nodes/index.ts +14 -4
- package/src/nodes/operation.ts +47 -9
- package/src/nodes/parameter.ts +27 -1
- package/src/nodes/property.ts +23 -1
- package/src/nodes/response.ts +23 -3
- package/src/nodes/root.ts +29 -8
- package/src/nodes/schema.ts +298 -36
- package/src/{functionPrinter.ts → printers/functionPrinter.ts} +20 -19
- package/src/printers/index.ts +3 -0
- package/src/{printer.ts → printers/printer.ts} +58 -42
- package/src/refs.ts +30 -6
- package/src/resolvers.ts +45 -0
- package/src/transformers.ts +196 -0
- package/src/types.ts +2 -1
- package/src/utils.ts +37 -8
- package/src/visitor.ts +204 -18
- package/dist/visitor-YMltBj6w.d.ts +0 -970
- package/src/transforms.ts +0 -114
package/dist/index.js
CHANGED
|
@@ -16,6 +16,17 @@ const nodeKinds = {
|
|
|
16
16
|
objectBindingParameter: "ObjectBindingParameter",
|
|
17
17
|
functionParameters: "FunctionParameters"
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Canonical schema type strings used by AST schema nodes.
|
|
21
|
+
*
|
|
22
|
+
* These values are used across the AST as stable discriminators
|
|
23
|
+
* (for example `schema.type === schemaTypes.object`).
|
|
24
|
+
*
|
|
25
|
+
* The map is grouped by intent:
|
|
26
|
+
* - primitives (`string`, `number`, `boolean`, ...)
|
|
27
|
+
* - structural/composite (`object`, `array`, `union`, ...)
|
|
28
|
+
* - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
|
|
29
|
+
*/
|
|
19
30
|
const schemaTypes = {
|
|
20
31
|
string: "string",
|
|
21
32
|
number: "number",
|
|
@@ -43,7 +54,7 @@ const schemaTypes = {
|
|
|
43
54
|
never: "never"
|
|
44
55
|
};
|
|
45
56
|
/**
|
|
46
|
-
*
|
|
57
|
+
* Primitive scalar schema types used when simplifying union members.
|
|
47
58
|
*/
|
|
48
59
|
const SCALAR_PRIMITIVE_TYPES = new Set([
|
|
49
60
|
"string",
|
|
@@ -84,818 +95,945 @@ const mediaTypes = {
|
|
|
84
95
|
videoMp4: "video/mp4"
|
|
85
96
|
};
|
|
86
97
|
//#endregion
|
|
87
|
-
//#region
|
|
98
|
+
//#region ../../internals/utils/dist/index.js
|
|
88
99
|
/**
|
|
89
|
-
*
|
|
100
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
101
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
102
|
+
* and capitalizes each word according to `pascal`.
|
|
103
|
+
*
|
|
104
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
90
105
|
*/
|
|
91
|
-
function
|
|
92
|
-
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
106
|
+
function toCamelOrPascal(text, pascal) {
|
|
107
|
+
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
108
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
109
|
+
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
110
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
111
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
98
112
|
}
|
|
99
113
|
/**
|
|
100
|
-
*
|
|
114
|
+
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
115
|
+
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
116
|
+
* Segments are joined with `/` to form a file path.
|
|
117
|
+
*
|
|
118
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
119
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
101
120
|
*/
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
parameters: [],
|
|
106
|
-
responses: [],
|
|
107
|
-
...props,
|
|
108
|
-
kind: "Operation"
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
function createSchema(props) {
|
|
112
|
-
if (props["type"] === "object") return {
|
|
113
|
-
properties: [],
|
|
114
|
-
...props,
|
|
115
|
-
kind: "Schema"
|
|
116
|
-
};
|
|
117
|
-
return {
|
|
118
|
-
...props,
|
|
119
|
-
kind: "Schema"
|
|
120
|
-
};
|
|
121
|
+
function applyToFileParts(text, transformPart) {
|
|
122
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
123
|
+
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
121
124
|
}
|
|
122
125
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
126
|
+
* Converts `text` to camelCase.
|
|
127
|
+
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
131
|
+
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
125
132
|
*/
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
133
|
+
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
134
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
135
|
+
prefix,
|
|
136
|
+
suffix
|
|
137
|
+
} : {}));
|
|
138
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
133
139
|
}
|
|
134
140
|
/**
|
|
135
|
-
*
|
|
136
|
-
* `
|
|
141
|
+
* Converts `text` to PascalCase.
|
|
142
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
146
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
137
147
|
*/
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
schema: syncPropertySchema(required, props.schema)
|
|
145
|
-
};
|
|
148
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
149
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
150
|
+
prefix,
|
|
151
|
+
suffix
|
|
152
|
+
}) : camelCase(part));
|
|
153
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
146
154
|
}
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*/
|
|
151
|
-
function createParameter(props) {
|
|
152
|
-
const required = props.required ?? false;
|
|
153
|
-
return {
|
|
154
|
-
...props,
|
|
155
|
-
kind: "Parameter",
|
|
156
|
-
required,
|
|
157
|
-
schema: syncPropertySchema(required, props.schema)
|
|
158
|
-
};
|
|
155
|
+
/** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
|
|
156
|
+
function defineCLIAdapter(adapter) {
|
|
157
|
+
return adapter;
|
|
159
158
|
}
|
|
160
159
|
/**
|
|
161
|
-
*
|
|
160
|
+
* Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
|
|
161
|
+
* Use to expose CLI capabilities to AI agents or MCP tools.
|
|
162
162
|
*/
|
|
163
|
-
function
|
|
164
|
-
return
|
|
165
|
-
...props,
|
|
166
|
-
kind: "Response"
|
|
167
|
-
};
|
|
163
|
+
function getCommandSchema(defs) {
|
|
164
|
+
return defs.map(serializeCommand);
|
|
168
165
|
}
|
|
169
|
-
|
|
170
|
-
* Creates a `FunctionParameterNode`. `optional` defaults to `false`.
|
|
171
|
-
*
|
|
172
|
-
* @example Required typed param
|
|
173
|
-
* ```ts
|
|
174
|
-
* createFunctionParameter({ name: 'petId', type: 'string' })
|
|
175
|
-
* // → petId: string
|
|
176
|
-
* ```
|
|
177
|
-
*
|
|
178
|
-
* @example Optional param
|
|
179
|
-
* ```ts
|
|
180
|
-
* createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
|
|
181
|
-
* // → params?: QueryParams
|
|
182
|
-
* ```
|
|
183
|
-
*
|
|
184
|
-
* @example Param with default (implicitly optional — cannot combine with `optional: true`)
|
|
185
|
-
* ```ts
|
|
186
|
-
* createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
|
|
187
|
-
* // → config: RequestConfig = {}
|
|
188
|
-
* ```
|
|
189
|
-
*/
|
|
190
|
-
function createFunctionParameter(props) {
|
|
166
|
+
function serializeCommand(def) {
|
|
191
167
|
return {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
168
|
+
name: def.name,
|
|
169
|
+
description: def.description,
|
|
170
|
+
arguments: def.arguments,
|
|
171
|
+
options: serializeOptions(def.options ?? {}),
|
|
172
|
+
subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
|
|
195
173
|
};
|
|
196
174
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* // call → { id, name }
|
|
211
|
-
* ```
|
|
212
|
-
*
|
|
213
|
-
* @example Inline — children emitted as individual top-level params
|
|
214
|
-
* ```ts
|
|
215
|
-
* createObjectBindingParameter({
|
|
216
|
-
* properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
|
|
217
|
-
* inline: true,
|
|
218
|
-
* })
|
|
219
|
-
* // declaration → petId: string
|
|
220
|
-
* // call → petId
|
|
221
|
-
* ```
|
|
222
|
-
*/
|
|
223
|
-
function createObjectBindingParameter(props) {
|
|
224
|
-
return {
|
|
225
|
-
...props,
|
|
226
|
-
kind: "ObjectBindingParameter"
|
|
227
|
-
};
|
|
175
|
+
function serializeOptions(options) {
|
|
176
|
+
return Object.entries(options).map(([name, opt]) => {
|
|
177
|
+
return {
|
|
178
|
+
name,
|
|
179
|
+
flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
|
|
180
|
+
type: opt.type,
|
|
181
|
+
description: opt.description,
|
|
182
|
+
...opt.default !== void 0 ? { default: opt.default } : {},
|
|
183
|
+
...opt.hint ? { hint: opt.hint } : {},
|
|
184
|
+
...opt.enum ? { enum: opt.enum } : {},
|
|
185
|
+
...opt.required ? { required: opt.required } : {}
|
|
186
|
+
};
|
|
187
|
+
});
|
|
228
188
|
}
|
|
229
|
-
/**
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
189
|
+
/** Prints formatted help output for a command using its `CommandDefinition`. */
|
|
190
|
+
function renderHelp(def, parentName) {
|
|
191
|
+
const schema = getCommandSchema([def])[0];
|
|
192
|
+
const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
|
|
193
|
+
const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
|
|
194
|
+
const subCmdPart = schema.subCommands.length ? " <command>" : "";
|
|
195
|
+
console.log(`\n${styleText("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
|
|
196
|
+
if (schema.description) console.log(` ${schema.description}\n`);
|
|
197
|
+
if (schema.subCommands.length) {
|
|
198
|
+
console.log(styleText("bold", "Commands:"));
|
|
199
|
+
for (const sub of schema.subCommands) console.log(` ${styleText("cyan", sub.name.padEnd(16))}${sub.description}`);
|
|
200
|
+
console.log();
|
|
201
|
+
}
|
|
202
|
+
const options = [...schema.options, {
|
|
203
|
+
name: "help",
|
|
204
|
+
flags: "-h, --help",
|
|
205
|
+
type: "boolean",
|
|
206
|
+
description: "Show help"
|
|
207
|
+
}];
|
|
208
|
+
console.log(styleText("bold", "Options:"));
|
|
209
|
+
for (const opt of options) {
|
|
210
|
+
const flags = styleText("cyan", opt.flags.padEnd(30));
|
|
211
|
+
const defaultPart = opt.default !== void 0 ? styleText("dim", ` (default: ${opt.default})`) : "";
|
|
212
|
+
console.log(` ${flags}${opt.description}${defaultPart}`);
|
|
213
|
+
}
|
|
214
|
+
console.log();
|
|
215
|
+
}
|
|
216
|
+
function buildParseOptions(def) {
|
|
217
|
+
const result = { help: {
|
|
218
|
+
type: "boolean",
|
|
219
|
+
short: "h"
|
|
220
|
+
} };
|
|
221
|
+
for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
|
|
222
|
+
type: opt.type,
|
|
223
|
+
...opt.short ? { short: opt.short } : {},
|
|
224
|
+
...opt.default !== void 0 ? { default: opt.default } : {}
|
|
247
225
|
};
|
|
226
|
+
return result;
|
|
248
227
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return createPrinterFactory((node) => node.type)(build);
|
|
228
|
+
async function runCommand(def, argv, parentName) {
|
|
229
|
+
const parseOptions = buildParseOptions(def);
|
|
230
|
+
let parsed;
|
|
231
|
+
try {
|
|
232
|
+
const result = parseArgs({
|
|
233
|
+
args: argv,
|
|
234
|
+
options: parseOptions,
|
|
235
|
+
allowPositionals: true,
|
|
236
|
+
strict: false
|
|
237
|
+
});
|
|
238
|
+
parsed = {
|
|
239
|
+
values: result.values,
|
|
240
|
+
positionals: result.positionals
|
|
241
|
+
};
|
|
242
|
+
} catch {
|
|
243
|
+
renderHelp(def, parentName);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
if (parsed.values["help"]) {
|
|
247
|
+
renderHelp(def, parentName);
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
|
|
251
|
+
console.error(styleText("red", `Error: --${name} is required`));
|
|
252
|
+
renderHelp(def, parentName);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
if (!def.run) {
|
|
256
|
+
renderHelp(def, parentName);
|
|
257
|
+
process.exit(0);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
await def.run(parsed);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.error(styleText("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
263
|
+
renderHelp(def, parentName);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function printRootHelp(programName, version, defs) {
|
|
268
|
+
console.log(`\n${styleText("bold", "Usage:")} ${programName} <command> [options]\n`);
|
|
269
|
+
console.log(` Kubb generation — v${version}\n`);
|
|
270
|
+
console.log(styleText("bold", "Commands:"));
|
|
271
|
+
for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
|
|
272
|
+
console.log();
|
|
273
|
+
console.log(styleText("bold", "Options:"));
|
|
274
|
+
console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
|
|
275
|
+
console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
|
|
276
|
+
console.log();
|
|
277
|
+
console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
|
|
300
278
|
}
|
|
279
|
+
defineCLIAdapter({
|
|
280
|
+
renderHelp(def, parentName) {
|
|
281
|
+
renderHelp(def, parentName);
|
|
282
|
+
},
|
|
283
|
+
async run(defs, argv, opts) {
|
|
284
|
+
const { programName, defaultCommandName, version } = opts;
|
|
285
|
+
const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
|
|
286
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
287
|
+
console.log(version);
|
|
288
|
+
process.exit(0);
|
|
289
|
+
}
|
|
290
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
291
|
+
printRootHelp(programName, version, defs);
|
|
292
|
+
process.exit(0);
|
|
293
|
+
}
|
|
294
|
+
if (args.length === 0) {
|
|
295
|
+
const defaultDef = defs.find((d) => d.name === defaultCommandName);
|
|
296
|
+
if (defaultDef?.run) await runCommand(defaultDef, [], programName);
|
|
297
|
+
else printRootHelp(programName, version, defs);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const [first, ...rest] = args;
|
|
301
|
+
const isKnownSubcommand = defs.some((d) => d.name === first);
|
|
302
|
+
let def;
|
|
303
|
+
let commandArgv;
|
|
304
|
+
let parentName;
|
|
305
|
+
if (isKnownSubcommand) {
|
|
306
|
+
def = defs.find((d) => d.name === first);
|
|
307
|
+
commandArgv = rest;
|
|
308
|
+
parentName = programName;
|
|
309
|
+
} else {
|
|
310
|
+
def = defs.find((d) => d.name === defaultCommandName);
|
|
311
|
+
commandArgv = args;
|
|
312
|
+
parentName = programName;
|
|
313
|
+
}
|
|
314
|
+
if (!def) {
|
|
315
|
+
console.error(`Unknown command: ${first}`);
|
|
316
|
+
printRootHelp(programName, version, defs);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
if (def.subCommands?.length) {
|
|
320
|
+
const [subName, ...subRest] = commandArgv;
|
|
321
|
+
const subDef = def.subCommands.find((s) => s.name === subName);
|
|
322
|
+
if (subName === "--help" || subName === "-h") {
|
|
323
|
+
renderHelp(def, parentName);
|
|
324
|
+
process.exit(0);
|
|
325
|
+
}
|
|
326
|
+
if (!subDef) {
|
|
327
|
+
renderHelp(def, parentName);
|
|
328
|
+
process.exit(subName ? 1 : 0);
|
|
329
|
+
}
|
|
330
|
+
await runCommand(subDef, subRest, `${parentName} ${def.name}`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
await runCommand(def, commandArgv, parentName);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
301
336
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* @param getKey — derives the handler-map key from a node. Return `undefined` to skip.
|
|
306
|
-
*
|
|
307
|
-
* @example
|
|
308
|
-
* ```ts
|
|
309
|
-
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
310
|
-
* (node) => kindToHandlerKey[node.kind],
|
|
311
|
-
* )
|
|
312
|
-
* ```
|
|
337
|
+
* Parses a CSS hex color string (`#RGB`) into its RGB channels.
|
|
338
|
+
* Falls back to `255` for any channel that cannot be parsed.
|
|
313
339
|
*/
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (!handler) return void 0;
|
|
325
|
-
return handler.call(context, node);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
return {
|
|
329
|
-
name,
|
|
330
|
-
options: resolvedOptions,
|
|
331
|
-
print: printOverride ? printOverride.bind(context) : context.print
|
|
332
|
-
};
|
|
333
|
-
};
|
|
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
|
|
334
350
|
};
|
|
335
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");
|
|
361
|
+
/**
|
|
362
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
363
|
+
*/
|
|
364
|
+
function isValidVarName(name) {
|
|
365
|
+
try {
|
|
366
|
+
new Function(`var ${name}`);
|
|
367
|
+
} catch {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
336
372
|
//#endregion
|
|
337
|
-
//#region src/
|
|
338
|
-
const kindToHandlerKey = {
|
|
339
|
-
FunctionParameter: "functionParameter",
|
|
340
|
-
ObjectBindingParameter: "objectBindingParameter",
|
|
341
|
-
FunctionParameters: "functionParameters"
|
|
342
|
-
};
|
|
373
|
+
//#region src/guards.ts
|
|
343
374
|
/**
|
|
344
|
-
*
|
|
345
|
-
* Built on `createPrinterFactory` — dispatches on `node.kind` instead of `node.type`.
|
|
375
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
346
376
|
*
|
|
347
377
|
* @example
|
|
348
378
|
* ```ts
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
|
|
352
|
-
* name: 'my',
|
|
353
|
-
* options,
|
|
354
|
-
* nodes: {
|
|
355
|
-
* functionParameter(node) {
|
|
356
|
-
* return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
|
|
357
|
-
* },
|
|
358
|
-
* objectBindingParameter(node) {
|
|
359
|
-
* const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
|
|
360
|
-
* return `{ ${inner} }`
|
|
361
|
-
* },
|
|
362
|
-
* functionParameters(node) {
|
|
363
|
-
* return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
|
|
364
|
-
* },
|
|
365
|
-
* },
|
|
366
|
-
* }))
|
|
379
|
+
* const schema = createSchema({ type: 'string' })
|
|
380
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
367
381
|
* ```
|
|
368
382
|
*/
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (param.kind === "ObjectBindingParameter") {
|
|
372
|
-
if (param.default) return 2;
|
|
373
|
-
return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
|
|
374
|
-
}
|
|
375
|
-
if (param.rest) return 3;
|
|
376
|
-
if (param.default) return 2;
|
|
377
|
-
return param.optional ? 1 : 0;
|
|
378
|
-
}
|
|
379
|
-
function sortParams(params) {
|
|
380
|
-
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
383
|
+
function narrowSchema(node, type) {
|
|
384
|
+
return node?.type === type ? node : void 0;
|
|
381
385
|
}
|
|
382
|
-
function
|
|
383
|
-
return
|
|
386
|
+
function isKind(kind) {
|
|
387
|
+
return (node) => node.kind === kind;
|
|
384
388
|
}
|
|
385
389
|
/**
|
|
386
|
-
*
|
|
387
|
-
* used throughout Kubb plugins.
|
|
390
|
+
* Returns `true` when the input is a `RootNode`.
|
|
388
391
|
*
|
|
389
392
|
* @example
|
|
390
393
|
* ```ts
|
|
391
|
-
*
|
|
394
|
+
* if (isRootNode(node)) {
|
|
395
|
+
* console.log(node.schemas.length)
|
|
396
|
+
* }
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
const isRootNode = isKind("Root");
|
|
400
|
+
/**
|
|
401
|
+
* Returns `true` when the input is an `OperationNode`.
|
|
392
402
|
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* if (isOperationNode(node)) {
|
|
406
|
+
* console.log(node.operationId)
|
|
407
|
+
* }
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
const isOperationNode = isKind("Operation");
|
|
411
|
+
/**
|
|
412
|
+
* Returns `true` when the input is a `SchemaNode`.
|
|
399
413
|
*
|
|
400
|
-
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```ts
|
|
416
|
+
* if (isSchemaNode(node)) {
|
|
417
|
+
* console.log(node.type)
|
|
418
|
+
* }
|
|
401
419
|
* ```
|
|
402
420
|
*/
|
|
403
|
-
const functionPrinter = defineFunctionPrinter((options) => ({
|
|
404
|
-
name: "functionParameters",
|
|
405
|
-
options,
|
|
406
|
-
nodes: {
|
|
407
|
-
functionParameter(node) {
|
|
408
|
-
const { mode, transformName, transformType } = this.options;
|
|
409
|
-
const name = transformName ? transformName(node.name) : node.name;
|
|
410
|
-
const type = node.type && transformType ? transformType(node.type) : node.type;
|
|
411
|
-
if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
|
|
412
|
-
if (mode === "call") return node.rest ? `...${name}` : name;
|
|
413
|
-
if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
|
|
414
|
-
if (type) {
|
|
415
|
-
if (node.optional) return `${name}?: ${type}`;
|
|
416
|
-
return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
|
|
417
|
-
}
|
|
418
|
-
return node.default ? `${name} = ${node.default}` : name;
|
|
419
|
-
},
|
|
420
|
-
objectBindingParameter(node) {
|
|
421
|
-
const { mode, transformName, transformType } = this.options;
|
|
422
|
-
const sorted = sortChildParams(node.properties);
|
|
423
|
-
const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
|
|
424
|
-
if (node.inline) return sorted.map((p) => this.print(p)).filter(Boolean).join(", ");
|
|
425
|
-
if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
426
|
-
if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
427
|
-
const names = sorted.map((p) => {
|
|
428
|
-
return transformName ? transformName(p.name) : p.name;
|
|
429
|
-
});
|
|
430
|
-
const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
|
|
431
|
-
if (!nameStr) return null;
|
|
432
|
-
let typeAnnotation = node.type;
|
|
433
|
-
if (!typeAnnotation) {
|
|
434
|
-
const typeParts = sorted.filter((p) => p.type).map((p) => {
|
|
435
|
-
const t = transformType && p.type ? transformType(p.type) : p.type;
|
|
436
|
-
return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
|
|
437
|
-
});
|
|
438
|
-
typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
|
|
439
|
-
}
|
|
440
|
-
if (typeAnnotation) {
|
|
441
|
-
if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
|
|
442
|
-
return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
|
|
443
|
-
}
|
|
444
|
-
return node.default ? `${nameStr} = ${node.default}` : nameStr;
|
|
445
|
-
},
|
|
446
|
-
functionParameters(node) {
|
|
447
|
-
return sortParams(node.params).map((p) => this.print(p)).filter(Boolean).join(", ");
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}));
|
|
451
|
-
//#endregion
|
|
452
|
-
//#region src/guards.ts
|
|
453
|
-
/**
|
|
454
|
-
* Narrows a `SchemaNode` to the specific variant matching `type`.
|
|
455
|
-
*/
|
|
456
|
-
function narrowSchema(node, type) {
|
|
457
|
-
return node?.type === type ? node : void 0;
|
|
458
|
-
}
|
|
459
|
-
function isKind(kind) {
|
|
460
|
-
return (node) => node.kind === kind;
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Type guard for `RootNode`.
|
|
464
|
-
*/
|
|
465
|
-
const isRootNode = isKind("Root");
|
|
466
|
-
/**
|
|
467
|
-
* Type guard for `OperationNode`.
|
|
468
|
-
*/
|
|
469
|
-
const isOperationNode = isKind("Operation");
|
|
470
|
-
/**
|
|
471
|
-
* Type guard for `SchemaNode`.
|
|
472
|
-
*/
|
|
473
421
|
const isSchemaNode = isKind("Schema");
|
|
474
422
|
/**
|
|
475
|
-
*
|
|
423
|
+
* Returns `true` when the input is a `PropertyNode`.
|
|
476
424
|
*/
|
|
477
425
|
const isPropertyNode = isKind("Property");
|
|
478
426
|
/**
|
|
479
|
-
*
|
|
427
|
+
* Returns `true` when the input is a `ParameterNode`.
|
|
480
428
|
*/
|
|
481
429
|
const isParameterNode = isKind("Parameter");
|
|
482
430
|
/**
|
|
483
|
-
*
|
|
431
|
+
* Returns `true` when the input is a `ResponseNode`.
|
|
484
432
|
*/
|
|
485
433
|
const isResponseNode = isKind("Response");
|
|
486
434
|
/**
|
|
487
|
-
*
|
|
435
|
+
* Returns `true` when the input is a `FunctionParameterNode`.
|
|
488
436
|
*/
|
|
489
437
|
const isFunctionParameterNode = isKind("FunctionParameter");
|
|
490
438
|
/**
|
|
491
|
-
*
|
|
439
|
+
* Returns `true` when the input is an `ObjectBindingParameterNode`.
|
|
492
440
|
*/
|
|
493
441
|
const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
|
|
494
442
|
/**
|
|
495
|
-
*
|
|
443
|
+
* Returns `true` when the input is a `FunctionParametersNode`.
|
|
496
444
|
*/
|
|
497
445
|
const isFunctionParametersNode = isKind("FunctionParameters");
|
|
498
446
|
//#endregion
|
|
499
|
-
//#region src/
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
447
|
+
//#region src/utils.ts
|
|
448
|
+
const plainStringTypes = new Set([
|
|
449
|
+
"string",
|
|
450
|
+
"uuid",
|
|
451
|
+
"email",
|
|
452
|
+
"url",
|
|
453
|
+
"datetime"
|
|
454
|
+
]);
|
|
507
455
|
/**
|
|
508
|
-
*
|
|
456
|
+
* Returns `true` when a schema is emitted as a plain TypeScript `string`.
|
|
457
|
+
*
|
|
458
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
459
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```ts
|
|
463
|
+
* isStringType(createSchema({ type: 'uuid' })) // true
|
|
464
|
+
* isStringType(createSchema({ type: 'date', representation: 'date' })) // false
|
|
465
|
+
* ```
|
|
509
466
|
*/
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return
|
|
467
|
+
function isStringType(node) {
|
|
468
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
469
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
470
|
+
if (temporal) return temporal.representation !== "date";
|
|
471
|
+
return false;
|
|
514
472
|
}
|
|
515
473
|
/**
|
|
516
|
-
*
|
|
474
|
+
* Applies casing rules to parameter names and returns a new parameter array.
|
|
475
|
+
*
|
|
476
|
+
* The input array is not mutated.
|
|
477
|
+
* If `casing` is not set, the original array is returned unchanged.
|
|
478
|
+
*
|
|
479
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
480
|
+
* in generated output match the desired casing while preserving
|
|
481
|
+
* `OperationNode.parameters` for other consumers.
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```ts
|
|
485
|
+
* const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
|
|
486
|
+
* const cased = caseParams(params, 'camelcase')
|
|
487
|
+
* // cased[0].name === 'petId'
|
|
488
|
+
* ```
|
|
517
489
|
*/
|
|
518
|
-
function
|
|
519
|
-
|
|
490
|
+
function caseParams(params, casing) {
|
|
491
|
+
if (!casing) return params;
|
|
492
|
+
return params.map((param) => {
|
|
493
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
494
|
+
return {
|
|
495
|
+
...param,
|
|
496
|
+
name: transformed
|
|
497
|
+
};
|
|
498
|
+
});
|
|
520
499
|
}
|
|
521
500
|
/**
|
|
522
|
-
*
|
|
501
|
+
* Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
|
|
502
|
+
*
|
|
503
|
+
* - `optional` is set for non-required, non-nullable schemas.
|
|
504
|
+
* - `nullish` is set for non-required, nullable schemas.
|
|
523
505
|
*/
|
|
524
|
-
function
|
|
525
|
-
|
|
506
|
+
function syncOptionality(required, schema) {
|
|
507
|
+
const nullable = schema.nullable ?? false;
|
|
508
|
+
return {
|
|
509
|
+
...schema,
|
|
510
|
+
optional: !required && !nullable ? true : void 0,
|
|
511
|
+
nullish: !required && nullable ? true : void 0
|
|
512
|
+
};
|
|
526
513
|
}
|
|
527
514
|
//#endregion
|
|
528
|
-
//#region src/
|
|
515
|
+
//#region src/factory.ts
|
|
529
516
|
/**
|
|
530
|
-
*
|
|
531
|
-
*
|
|
517
|
+
* Creates a `RootNode` with stable defaults for `schemas` and `operations`.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```ts
|
|
521
|
+
* const root = createRoot()
|
|
522
|
+
* // { kind: 'Root', schemas: [], operations: [] }
|
|
523
|
+
* ```
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```ts
|
|
527
|
+
* const root = createRoot({ schemas: [petSchema] })
|
|
528
|
+
* // keeps default operations: []
|
|
529
|
+
* ```
|
|
532
530
|
*/
|
|
533
|
-
function
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (prop.name !== propertyName) return prop;
|
|
541
|
-
return createProperty({
|
|
542
|
-
...prop,
|
|
543
|
-
schema: createSchema({
|
|
544
|
-
type: "enum",
|
|
545
|
-
primitive: "string",
|
|
546
|
-
enumValues: values,
|
|
547
|
-
name: enumName,
|
|
548
|
-
readOnly: prop.schema.readOnly,
|
|
549
|
-
writeOnly: prop.schema.writeOnly
|
|
550
|
-
})
|
|
551
|
-
});
|
|
552
|
-
})
|
|
553
|
-
});
|
|
531
|
+
function createRoot(overrides = {}) {
|
|
532
|
+
return {
|
|
533
|
+
schemas: [],
|
|
534
|
+
operations: [],
|
|
535
|
+
...overrides,
|
|
536
|
+
kind: "Root"
|
|
537
|
+
};
|
|
554
538
|
}
|
|
555
539
|
/**
|
|
556
|
-
*
|
|
540
|
+
* Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```ts
|
|
544
|
+
* const operation = createOperation({
|
|
545
|
+
* operationId: 'getPetById',
|
|
546
|
+
* method: 'GET',
|
|
547
|
+
* path: '/pet/{petId}',
|
|
548
|
+
* })
|
|
549
|
+
* // tags, parameters, and responses are []
|
|
550
|
+
* ```
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* ```ts
|
|
554
|
+
* const operation = createOperation({
|
|
555
|
+
* operationId: 'findPets',
|
|
556
|
+
* method: 'GET',
|
|
557
|
+
* path: '/pet/findByStatus',
|
|
558
|
+
* tags: ['pet'],
|
|
559
|
+
* })
|
|
560
|
+
* ```
|
|
557
561
|
*/
|
|
558
|
-
function
|
|
559
|
-
return
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
...previousObject,
|
|
567
|
-
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
568
|
-
});
|
|
569
|
-
return acc;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
acc.push(member);
|
|
573
|
-
return acc;
|
|
574
|
-
}, []);
|
|
562
|
+
function createOperation(props) {
|
|
563
|
+
return {
|
|
564
|
+
tags: [],
|
|
565
|
+
parameters: [],
|
|
566
|
+
responses: [],
|
|
567
|
+
...props,
|
|
568
|
+
kind: "Operation"
|
|
569
|
+
};
|
|
575
570
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
return
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (!primitive) return true;
|
|
587
|
-
if (!enumNode.enumType) return true;
|
|
588
|
-
if (scalarPrimitives.has(primitive)) return false;
|
|
589
|
-
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
590
|
-
return true;
|
|
591
|
-
});
|
|
571
|
+
function createSchema(props) {
|
|
572
|
+
if (props["type"] === "object") return {
|
|
573
|
+
properties: [],
|
|
574
|
+
...props,
|
|
575
|
+
kind: "Schema"
|
|
576
|
+
};
|
|
577
|
+
return {
|
|
578
|
+
...props,
|
|
579
|
+
kind: "Schema"
|
|
580
|
+
};
|
|
592
581
|
}
|
|
593
|
-
//#endregion
|
|
594
|
-
//#region ../../internals/utils/dist/index.js
|
|
595
582
|
/**
|
|
596
|
-
*
|
|
597
|
-
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
598
|
-
* and capitalizes each word according to `pascal`.
|
|
583
|
+
* Creates a `PropertyNode`.
|
|
599
584
|
*
|
|
600
|
-
*
|
|
585
|
+
* `required` defaults to `false`.
|
|
586
|
+
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```ts
|
|
590
|
+
* const property = createProperty({
|
|
591
|
+
* name: 'status',
|
|
592
|
+
* schema: createSchema({ type: 'string' }),
|
|
593
|
+
* })
|
|
594
|
+
* // required=false, schema.optional=true
|
|
595
|
+
* ```
|
|
596
|
+
*
|
|
597
|
+
* @example
|
|
598
|
+
* ```ts
|
|
599
|
+
* const property = createProperty({
|
|
600
|
+
* name: 'status',
|
|
601
|
+
* required: true,
|
|
602
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
603
|
+
* })
|
|
604
|
+
* // required=true, no optional/nullish
|
|
605
|
+
* ```
|
|
601
606
|
*/
|
|
602
|
-
function
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
607
|
+
function createProperty(props) {
|
|
608
|
+
const required = props.required ?? false;
|
|
609
|
+
return {
|
|
610
|
+
...props,
|
|
611
|
+
kind: "Property",
|
|
612
|
+
required,
|
|
613
|
+
schema: syncOptionality(required, props.schema)
|
|
614
|
+
};
|
|
608
615
|
}
|
|
609
616
|
/**
|
|
610
|
-
*
|
|
611
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
612
|
-
* Segments are joined with `/` to form a file path.
|
|
617
|
+
* Creates a `ParameterNode`.
|
|
613
618
|
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
619
|
+
* `required` defaults to `false`.
|
|
620
|
+
* Nested schema flags are set from `required` and `schema.nullable`.
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* const param = createParameter({
|
|
625
|
+
* name: 'petId',
|
|
626
|
+
* in: 'path',
|
|
627
|
+
* required: true,
|
|
628
|
+
* schema: createSchema({ type: 'string' }),
|
|
629
|
+
* })
|
|
630
|
+
* ```
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* const param = createParameter({
|
|
635
|
+
* name: 'status',
|
|
636
|
+
* in: 'query',
|
|
637
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
638
|
+
* })
|
|
639
|
+
* // required=false, schema.nullish=true
|
|
640
|
+
* ```
|
|
616
641
|
*/
|
|
617
|
-
function
|
|
618
|
-
const
|
|
619
|
-
return
|
|
642
|
+
function createParameter(props) {
|
|
643
|
+
const required = props.required ?? false;
|
|
644
|
+
return {
|
|
645
|
+
...props,
|
|
646
|
+
kind: "Parameter",
|
|
647
|
+
required,
|
|
648
|
+
schema: syncOptionality(required, props.schema)
|
|
649
|
+
};
|
|
620
650
|
}
|
|
621
651
|
/**
|
|
622
|
-
*
|
|
623
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
652
|
+
* Creates a `ResponseNode`.
|
|
624
653
|
*
|
|
625
654
|
* @example
|
|
626
|
-
*
|
|
627
|
-
*
|
|
655
|
+
* ```ts
|
|
656
|
+
* const response = createResponse({
|
|
657
|
+
* statusCode: '200',
|
|
658
|
+
* description: 'Success',
|
|
659
|
+
* schema: createSchema({ type: 'object', properties: [] }),
|
|
660
|
+
* })
|
|
661
|
+
* ```
|
|
628
662
|
*/
|
|
629
|
-
function
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
663
|
+
function createResponse(props) {
|
|
664
|
+
return {
|
|
665
|
+
...props,
|
|
666
|
+
kind: "Response"
|
|
667
|
+
};
|
|
635
668
|
}
|
|
636
|
-
/**
|
|
637
|
-
|
|
638
|
-
|
|
669
|
+
/**
|
|
670
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```ts
|
|
674
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
675
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
679
|
+
return createSchema({
|
|
680
|
+
type: "object",
|
|
681
|
+
primitive: "object",
|
|
682
|
+
properties: [createProperty({
|
|
683
|
+
name: propertyName,
|
|
684
|
+
schema: createSchema({
|
|
685
|
+
type: "enum",
|
|
686
|
+
primitive: "string",
|
|
687
|
+
enumValues: [value]
|
|
688
|
+
}),
|
|
689
|
+
required: true
|
|
690
|
+
})]
|
|
691
|
+
});
|
|
639
692
|
}
|
|
640
693
|
/**
|
|
641
|
-
*
|
|
642
|
-
*
|
|
694
|
+
* Creates a `FunctionParameterNode`.
|
|
695
|
+
*
|
|
696
|
+
* `optional` defaults to `false`.
|
|
697
|
+
*
|
|
698
|
+
* @example Required typed param
|
|
699
|
+
* ```ts
|
|
700
|
+
* createFunctionParameter({ name: 'petId', type: 'string' })
|
|
701
|
+
* // → petId: string
|
|
702
|
+
* ```
|
|
703
|
+
*
|
|
704
|
+
* @example Optional param
|
|
705
|
+
* ```ts
|
|
706
|
+
* createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
|
|
707
|
+
* // → params?: QueryParams
|
|
708
|
+
* ```
|
|
709
|
+
*
|
|
710
|
+
* @example Param with default (implicitly optional; cannot combine with `optional: true`)
|
|
711
|
+
* ```ts
|
|
712
|
+
* createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
|
|
713
|
+
* // → config: RequestConfig = {}
|
|
714
|
+
* ```
|
|
643
715
|
*/
|
|
644
|
-
function
|
|
645
|
-
return
|
|
716
|
+
function createFunctionParameter(props) {
|
|
717
|
+
return {
|
|
718
|
+
optional: false,
|
|
719
|
+
...props,
|
|
720
|
+
kind: "FunctionParameter"
|
|
721
|
+
};
|
|
646
722
|
}
|
|
647
|
-
|
|
723
|
+
/**
|
|
724
|
+
* Creates an `ObjectBindingParameterNode` for object-destructured parameter groups.
|
|
725
|
+
*
|
|
726
|
+
* @example Destructured object param
|
|
727
|
+
* ```ts
|
|
728
|
+
* createObjectBindingParameter({
|
|
729
|
+
* properties: [
|
|
730
|
+
* createFunctionParameter({ name: 'id', type: 'string', optional: false }),
|
|
731
|
+
* createFunctionParameter({ name: 'name', type: 'string', optional: true }),
|
|
732
|
+
* ],
|
|
733
|
+
* default: '{}',
|
|
734
|
+
* })
|
|
735
|
+
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
736
|
+
* // call → { id, name }
|
|
737
|
+
* ```
|
|
738
|
+
*
|
|
739
|
+
* @example Inline mode — children emitted as individual top-level parameters
|
|
740
|
+
* ```ts
|
|
741
|
+
* createObjectBindingParameter({
|
|
742
|
+
* properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
|
|
743
|
+
* inline: true,
|
|
744
|
+
* })
|
|
745
|
+
* // declaration → petId: string
|
|
746
|
+
* // call → petId
|
|
747
|
+
* ```
|
|
748
|
+
*/
|
|
749
|
+
function createObjectBindingParameter(props) {
|
|
648
750
|
return {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
arguments: def.arguments,
|
|
652
|
-
options: serializeOptions(def.options ?? {}),
|
|
653
|
-
subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
|
|
751
|
+
...props,
|
|
752
|
+
kind: "ObjectBindingParameter"
|
|
654
753
|
};
|
|
655
754
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
755
|
+
/**
|
|
756
|
+
* Creates a `FunctionParametersNode` from an ordered list of parameters.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```ts
|
|
760
|
+
* createFunctionParameters({
|
|
761
|
+
* params: [
|
|
762
|
+
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
763
|
+
* createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
|
|
764
|
+
* ],
|
|
765
|
+
* })
|
|
766
|
+
* ```
|
|
767
|
+
*
|
|
768
|
+
* @example
|
|
769
|
+
* ```ts
|
|
770
|
+
* const empty = createFunctionParameters()
|
|
771
|
+
* // { kind: 'FunctionParameters', params: [] }
|
|
772
|
+
* ```
|
|
773
|
+
*/
|
|
774
|
+
function createFunctionParameters(props = {}) {
|
|
775
|
+
return {
|
|
776
|
+
params: [],
|
|
777
|
+
...props,
|
|
778
|
+
kind: "FunctionParameters"
|
|
779
|
+
};
|
|
669
780
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
781
|
+
//#endregion
|
|
782
|
+
//#region src/printers/printer.ts
|
|
783
|
+
/**
|
|
784
|
+
* Creates a schema printer factory.
|
|
785
|
+
*
|
|
786
|
+
* This function wraps a builder and makes options optional at call sites.
|
|
787
|
+
*
|
|
788
|
+
* The builder receives resolved options and returns:
|
|
789
|
+
* - `name` — a unique identifier for the printer
|
|
790
|
+
* - `options` — options stored on the returned printer instance
|
|
791
|
+
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
792
|
+
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
793
|
+
* - Inside this function, `this.print(node)` still dispatches to the `nodes` map
|
|
794
|
+
* - This keeps recursion safe and avoids self-calls
|
|
795
|
+
*
|
|
796
|
+
* When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
|
|
797
|
+
*
|
|
798
|
+
* @example Basic usage — Zod schema printer
|
|
799
|
+
* ```ts
|
|
800
|
+
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
801
|
+
*
|
|
802
|
+
* export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
|
|
803
|
+
* name: 'zod',
|
|
804
|
+
* options: { strict: options.strict ?? true },
|
|
805
|
+
* nodes: {
|
|
806
|
+
* string: () => 'z.string()',
|
|
807
|
+
* object(node) {
|
|
808
|
+
* const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
|
|
809
|
+
* return `z.object({ ${props} })`
|
|
810
|
+
* },
|
|
811
|
+
* },
|
|
812
|
+
* }))
|
|
813
|
+
* ```
|
|
814
|
+
*/
|
|
815
|
+
function definePrinter(build) {
|
|
816
|
+
return createPrinterFactory((node) => node.type)(build);
|
|
696
817
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
818
|
+
/**
|
|
819
|
+
* Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
|
|
820
|
+
**
|
|
821
|
+
* @example
|
|
822
|
+
* ```ts
|
|
823
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
824
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
825
|
+
* )
|
|
826
|
+
* ```
|
|
827
|
+
*/
|
|
828
|
+
function createPrinterFactory(getKey) {
|
|
829
|
+
return function(build) {
|
|
830
|
+
return (options) => {
|
|
831
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
|
|
832
|
+
const context = {
|
|
833
|
+
options: resolvedOptions,
|
|
834
|
+
print: (node) => {
|
|
835
|
+
const key = getKey(node);
|
|
836
|
+
if (key === void 0) return void 0;
|
|
837
|
+
const handler = nodes[key];
|
|
838
|
+
if (!handler) return void 0;
|
|
839
|
+
return handler.call(context, node);
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
return {
|
|
843
|
+
name,
|
|
844
|
+
options: resolvedOptions,
|
|
845
|
+
print: printOverride ? printOverride.bind(context) : context.print
|
|
846
|
+
};
|
|
847
|
+
};
|
|
706
848
|
};
|
|
707
|
-
return result;
|
|
708
849
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region src/printers/functionPrinter.ts
|
|
852
|
+
const kindToHandlerKey = {
|
|
853
|
+
FunctionParameter: "functionParameter",
|
|
854
|
+
ObjectBindingParameter: "objectBindingParameter",
|
|
855
|
+
FunctionParameters: "functionParameters"
|
|
856
|
+
};
|
|
857
|
+
/**
|
|
858
|
+
* Creates a function-parameter printer factory.
|
|
859
|
+
*
|
|
860
|
+
* This wrapper uses `createPrinterFactory` and dispatches handlers by `node.kind`
|
|
861
|
+
* (for function nodes) rather than by `node.type` (for schema nodes).
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```ts
|
|
865
|
+
* type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
|
|
866
|
+
*
|
|
867
|
+
* export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
|
|
868
|
+
* name: 'my',
|
|
869
|
+
* options,
|
|
870
|
+
* nodes: {
|
|
871
|
+
* functionParameter(node) {
|
|
872
|
+
* return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
|
|
873
|
+
* },
|
|
874
|
+
* objectBindingParameter(node) {
|
|
875
|
+
* const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
|
|
876
|
+
* return `{ ${inner} }`
|
|
877
|
+
* },
|
|
878
|
+
* functionParameters(node) {
|
|
879
|
+
* return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
|
|
880
|
+
* },
|
|
881
|
+
* },
|
|
882
|
+
* }))
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
|
|
886
|
+
function rank(param) {
|
|
887
|
+
if (param.kind === "ObjectBindingParameter") {
|
|
888
|
+
if (param.default) return 2;
|
|
889
|
+
return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
|
|
746
890
|
}
|
|
891
|
+
if (param.rest) return 3;
|
|
892
|
+
if (param.default) return 2;
|
|
893
|
+
return param.optional ? 1 : 0;
|
|
747
894
|
}
|
|
748
|
-
function
|
|
749
|
-
|
|
750
|
-
console.log(` Kubb generation — v${version}\n`);
|
|
751
|
-
console.log(styleText("bold", "Commands:"));
|
|
752
|
-
for (const def of defs) console.log(` ${styleText("cyan", def.name.padEnd(16))}${def.description}`);
|
|
753
|
-
console.log();
|
|
754
|
-
console.log(styleText("bold", "Options:"));
|
|
755
|
-
console.log(` ${styleText("cyan", "-v, --version".padEnd(30))}Show version number`);
|
|
756
|
-
console.log(` ${styleText("cyan", "-h, --help".padEnd(30))}Show help`);
|
|
757
|
-
console.log();
|
|
758
|
-
console.log(`Run ${styleText("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
|
|
895
|
+
function sortParams(params) {
|
|
896
|
+
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
759
897
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if (!def) {
|
|
796
|
-
console.error(`Unknown command: ${first}`);
|
|
797
|
-
printRootHelp(programName, version, defs);
|
|
798
|
-
process.exit(1);
|
|
799
|
-
}
|
|
800
|
-
if (def.subCommands?.length) {
|
|
801
|
-
const [subName, ...subRest] = commandArgv;
|
|
802
|
-
const subDef = def.subCommands.find((s) => s.name === subName);
|
|
803
|
-
if (subName === "--help" || subName === "-h") {
|
|
804
|
-
renderHelp(def, parentName);
|
|
805
|
-
process.exit(0);
|
|
898
|
+
function sortChildParams(params) {
|
|
899
|
+
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Default function-signature printer.
|
|
903
|
+
* Covers the four standard output modes used across Kubb plugins.
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* ```ts
|
|
907
|
+
* const printer = functionPrinter({ mode: 'declaration' })
|
|
908
|
+
*
|
|
909
|
+
* const sig = createFunctionParameters({
|
|
910
|
+
* params: [
|
|
911
|
+
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
912
|
+
* createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
|
|
913
|
+
* ],
|
|
914
|
+
* })
|
|
915
|
+
*
|
|
916
|
+
* printer.print(sig) // → "petId: string, config: Config = {}"
|
|
917
|
+
* ```
|
|
918
|
+
*/
|
|
919
|
+
const functionPrinter = defineFunctionPrinter((options) => ({
|
|
920
|
+
name: "functionParameters",
|
|
921
|
+
options,
|
|
922
|
+
nodes: {
|
|
923
|
+
functionParameter(node) {
|
|
924
|
+
const { mode, transformName, transformType } = this.options;
|
|
925
|
+
const name = transformName ? transformName(node.name) : node.name;
|
|
926
|
+
const type = node.type && transformType ? transformType(node.type) : node.type;
|
|
927
|
+
if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
|
|
928
|
+
if (mode === "call") return node.rest ? `...${name}` : name;
|
|
929
|
+
if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
|
|
930
|
+
if (type) {
|
|
931
|
+
if (node.optional) return `${name}?: ${type}`;
|
|
932
|
+
return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
|
|
806
933
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
934
|
+
return node.default ? `${name} = ${node.default}` : name;
|
|
935
|
+
},
|
|
936
|
+
objectBindingParameter(node) {
|
|
937
|
+
const { mode, transformName, transformType } = this.options;
|
|
938
|
+
const sorted = sortChildParams(node.properties);
|
|
939
|
+
const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
|
|
940
|
+
if (node.inline) return sorted.map((p) => this.print(p)).filter(Boolean).join(", ");
|
|
941
|
+
if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
942
|
+
if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
943
|
+
const names = sorted.map((p) => {
|
|
944
|
+
return transformName ? transformName(p.name) : p.name;
|
|
945
|
+
});
|
|
946
|
+
const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
|
|
947
|
+
if (!nameStr) return null;
|
|
948
|
+
let typeAnnotation = node.type;
|
|
949
|
+
if (!typeAnnotation) {
|
|
950
|
+
const typeParts = sorted.filter((p) => p.type).map((p) => {
|
|
951
|
+
const t = transformType && p.type ? transformType(p.type) : p.type;
|
|
952
|
+
return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
|
|
953
|
+
});
|
|
954
|
+
typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
|
|
810
955
|
}
|
|
811
|
-
|
|
812
|
-
|
|
956
|
+
if (typeAnnotation) {
|
|
957
|
+
if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
|
|
958
|
+
return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
|
|
959
|
+
}
|
|
960
|
+
return node.default ? `${nameStr} = ${node.default}` : nameStr;
|
|
961
|
+
},
|
|
962
|
+
functionParameters(node) {
|
|
963
|
+
return sortParams(node.params).map((p) => this.print(p)).filter(Boolean).join(", ");
|
|
813
964
|
}
|
|
814
|
-
await runCommand(def, commandArgv, parentName);
|
|
815
965
|
}
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
* Falls back to `255` for any channel that cannot be parsed.
|
|
820
|
-
*/
|
|
821
|
-
function parseHex(color) {
|
|
822
|
-
const int = Number.parseInt(color.replace("#", ""), 16);
|
|
823
|
-
return Number.isNaN(int) ? {
|
|
824
|
-
r: 255,
|
|
825
|
-
g: 255,
|
|
826
|
-
b: 255
|
|
827
|
-
} : {
|
|
828
|
-
r: int >> 16 & 255,
|
|
829
|
-
g: int >> 8 & 255,
|
|
830
|
-
b: int & 255
|
|
831
|
-
};
|
|
832
|
-
}
|
|
966
|
+
}));
|
|
967
|
+
//#endregion
|
|
968
|
+
//#region src/refs.ts
|
|
833
969
|
/**
|
|
834
|
-
* Returns
|
|
835
|
-
*
|
|
970
|
+
* Returns the last path segment of a reference string.
|
|
971
|
+
*
|
|
972
|
+
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
973
|
+
*
|
|
974
|
+
* @example
|
|
975
|
+
* ```ts
|
|
976
|
+
* extractRefName('#/components/schemas/Pet') // 'Pet'
|
|
977
|
+
* ```
|
|
836
978
|
*/
|
|
837
|
-
function
|
|
838
|
-
|
|
839
|
-
return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
|
|
979
|
+
function extractRefName(ref) {
|
|
980
|
+
return ref.split("/").at(-1) ?? ref;
|
|
840
981
|
}
|
|
841
|
-
hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
|
|
842
982
|
/**
|
|
843
|
-
*
|
|
983
|
+
* Builds a `RefMap` from `root.schemas` using each schema's `name`.
|
|
984
|
+
*
|
|
985
|
+
* Unnamed schemas are skipped.
|
|
986
|
+
*
|
|
987
|
+
* @example
|
|
988
|
+
* ```ts
|
|
989
|
+
* const refMap = buildRefMap(root)
|
|
990
|
+
* const pet = refMap.get('Pet')
|
|
991
|
+
* ```
|
|
844
992
|
*/
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
return true;
|
|
993
|
+
function buildRefMap(root) {
|
|
994
|
+
const map = /* @__PURE__ */ new Map();
|
|
995
|
+
for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
|
|
996
|
+
return map;
|
|
852
997
|
}
|
|
853
|
-
//#endregion
|
|
854
|
-
//#region src/utils.ts
|
|
855
|
-
const plainStringTypes = new Set([
|
|
856
|
-
"string",
|
|
857
|
-
"uuid",
|
|
858
|
-
"email",
|
|
859
|
-
"url",
|
|
860
|
-
"datetime"
|
|
861
|
-
]);
|
|
862
998
|
/**
|
|
863
|
-
*
|
|
999
|
+
* Resolves a schema by name from a `RefMap`.
|
|
864
1000
|
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* ```ts
|
|
1003
|
+
* const petSchema = resolveRef(refMap, 'Pet')
|
|
1004
|
+
* ```
|
|
867
1005
|
*/
|
|
868
|
-
function
|
|
869
|
-
|
|
870
|
-
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
871
|
-
if (temporal) return temporal.representation !== "date";
|
|
872
|
-
return false;
|
|
1006
|
+
function resolveRef(refMap, ref) {
|
|
1007
|
+
return refMap.get(ref);
|
|
873
1008
|
}
|
|
874
1009
|
/**
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
* The original `params` array is never mutated — a new array of cloned nodes is returned.
|
|
878
|
-
* When no `casing` is provided the original array is returned as-is.
|
|
1010
|
+
* Converts a `RefMap` into a plain object.
|
|
879
1011
|
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
1012
|
+
* @example
|
|
1013
|
+
* ```ts
|
|
1014
|
+
* const refsObject = refMapToObject(refMap)
|
|
1015
|
+
* ```
|
|
883
1016
|
*/
|
|
884
|
-
function
|
|
885
|
-
|
|
886
|
-
return params.map((param) => {
|
|
887
|
-
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
888
|
-
return {
|
|
889
|
-
...param,
|
|
890
|
-
name: transformed
|
|
891
|
-
};
|
|
892
|
-
});
|
|
1017
|
+
function refMapToObject(refMap) {
|
|
1018
|
+
return Object.fromEntries(refMap);
|
|
893
1019
|
}
|
|
894
1020
|
//#endregion
|
|
895
1021
|
//#region src/visitor.ts
|
|
896
1022
|
/**
|
|
897
|
-
* Creates a
|
|
898
|
-
*
|
|
1023
|
+
* Creates a small async concurrency limiter.
|
|
1024
|
+
*
|
|
1025
|
+
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
1026
|
+
*
|
|
1027
|
+
* @example
|
|
1028
|
+
* ```ts
|
|
1029
|
+
* const limit = createLimit(2)
|
|
1030
|
+
* await Promise.all([
|
|
1031
|
+
* limit(() => taskA()),
|
|
1032
|
+
* limit(() => taskB()),
|
|
1033
|
+
* limit(() => taskC()),
|
|
1034
|
+
* ])
|
|
1035
|
+
* // only 2 tasks run at the same time
|
|
1036
|
+
* ```
|
|
899
1037
|
*/
|
|
900
1038
|
function createLimit(concurrency) {
|
|
901
1039
|
let active = 0;
|
|
@@ -921,8 +1059,15 @@ function createLimit(concurrency) {
|
|
|
921
1059
|
/**
|
|
922
1060
|
* Returns the immediate traversable children of `node`.
|
|
923
1061
|
*
|
|
924
|
-
* For `Schema` nodes, children (properties
|
|
925
|
-
*
|
|
1062
|
+
* For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
|
|
1063
|
+
* `additionalProperties`) are only included
|
|
1064
|
+
* when `recurse` is `true`; shallow mode skips them.
|
|
1065
|
+
*
|
|
1066
|
+
* @example
|
|
1067
|
+
* ```ts
|
|
1068
|
+
* const children = getChildren(operationNode, true)
|
|
1069
|
+
* // returns parameters, requestBody schema (if present), and responses
|
|
1070
|
+
* ```
|
|
926
1071
|
*/
|
|
927
1072
|
function getChildren(node, recurse) {
|
|
928
1073
|
switch (node.kind) {
|
|
@@ -951,7 +1096,23 @@ function getChildren(node, recurse) {
|
|
|
951
1096
|
}
|
|
952
1097
|
/**
|
|
953
1098
|
* Depth-first traversal for side effects. Visitor return values are ignored.
|
|
954
|
-
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
1099
|
+
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
1100
|
+
* (default: `WALK_CONCURRENCY`).
|
|
1101
|
+
*
|
|
1102
|
+
* @example
|
|
1103
|
+
* ```ts
|
|
1104
|
+
* await walk(root, {
|
|
1105
|
+
* operation(node) {
|
|
1106
|
+
* console.log(node.operationId)
|
|
1107
|
+
* },
|
|
1108
|
+
* })
|
|
1109
|
+
* ```
|
|
1110
|
+
*
|
|
1111
|
+
* @example
|
|
1112
|
+
* ```ts
|
|
1113
|
+
* // Visit only the current node
|
|
1114
|
+
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
1115
|
+
* ```
|
|
955
1116
|
*/
|
|
956
1117
|
async function walk(node, options) {
|
|
957
1118
|
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
@@ -1084,8 +1245,18 @@ function transform(node, options) {
|
|
|
1084
1245
|
}
|
|
1085
1246
|
}
|
|
1086
1247
|
/**
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1248
|
+
* Composes multiple visitors into one visitor, applied left to right.
|
|
1249
|
+
*
|
|
1250
|
+
* For each node kind, output from one visitor is input to the next.
|
|
1251
|
+
* If a visitor returns `undefined`, the previous node value is kept.
|
|
1252
|
+
*
|
|
1253
|
+
* @example
|
|
1254
|
+
* ```ts
|
|
1255
|
+
* const visitor = composeTransformers(
|
|
1256
|
+
* { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
|
|
1257
|
+
* { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
|
|
1258
|
+
* )
|
|
1259
|
+
* ```
|
|
1089
1260
|
*/
|
|
1090
1261
|
function composeTransformers(...visitors) {
|
|
1091
1262
|
return {
|
|
@@ -1110,7 +1281,24 @@ function composeTransformers(...visitors) {
|
|
|
1110
1281
|
};
|
|
1111
1282
|
}
|
|
1112
1283
|
/**
|
|
1113
|
-
*
|
|
1284
|
+
* Runs a depth-first synchronous collection pass.
|
|
1285
|
+
*
|
|
1286
|
+
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
1287
|
+
*
|
|
1288
|
+
* @example
|
|
1289
|
+
* ```ts
|
|
1290
|
+
* const ids = collect(root, {
|
|
1291
|
+
* operation(node) {
|
|
1292
|
+
* return node.operationId
|
|
1293
|
+
* },
|
|
1294
|
+
* })
|
|
1295
|
+
* ```
|
|
1296
|
+
*
|
|
1297
|
+
* @example
|
|
1298
|
+
* ```ts
|
|
1299
|
+
* // Collect from only the current node
|
|
1300
|
+
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
1301
|
+
* ```
|
|
1114
1302
|
*/
|
|
1115
1303
|
function collect(node, options) {
|
|
1116
1304
|
const { depth, parent, ...visitor } = options;
|
|
@@ -1148,6 +1336,165 @@ function collect(node, options) {
|
|
|
1148
1336
|
return results;
|
|
1149
1337
|
}
|
|
1150
1338
|
//#endregion
|
|
1151
|
-
|
|
1339
|
+
//#region src/resolvers.ts
|
|
1340
|
+
function findDiscriminator(mapping, ref) {
|
|
1341
|
+
if (!mapping || !ref) return void 0;
|
|
1342
|
+
return Object.entries(mapping).find(([, value]) => value === ref)?.[0];
|
|
1343
|
+
}
|
|
1344
|
+
function childName(parentName, propName) {
|
|
1345
|
+
return parentName ? pascalCase([parentName, propName].join(" ")) : void 0;
|
|
1346
|
+
}
|
|
1347
|
+
function enumPropName(parentName, propName, enumSuffix) {
|
|
1348
|
+
return pascalCase([
|
|
1349
|
+
parentName,
|
|
1350
|
+
propName,
|
|
1351
|
+
enumSuffix
|
|
1352
|
+
].filter(Boolean).join(" "));
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Collects import entries for all `ref` schema nodes in `node`.
|
|
1356
|
+
*/
|
|
1357
|
+
function collectImports({ node, nameMapping, resolve }) {
|
|
1358
|
+
return collect(node, { schema(schemaNode) {
|
|
1359
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
1360
|
+
if (!schemaRef?.ref) return;
|
|
1361
|
+
const rawName = extractRefName(schemaRef.ref);
|
|
1362
|
+
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
1363
|
+
if (!result) return;
|
|
1364
|
+
return result;
|
|
1365
|
+
} });
|
|
1366
|
+
}
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region src/transformers.ts
|
|
1369
|
+
/**
|
|
1370
|
+
* Replaces a discriminator property's schema with a string enum of allowed values.
|
|
1371
|
+
*
|
|
1372
|
+
* If `node` is not an object schema, or if the property does not exist, the input
|
|
1373
|
+
* node is returned as-is.
|
|
1374
|
+
*
|
|
1375
|
+
* @example
|
|
1376
|
+
* ```ts
|
|
1377
|
+
* const schema = createSchema({
|
|
1378
|
+
* type: 'object',
|
|
1379
|
+
* properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
|
|
1380
|
+
* })
|
|
1381
|
+
* const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
|
|
1382
|
+
* ```
|
|
1383
|
+
*/
|
|
1384
|
+
function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
1385
|
+
const objectNode = narrowSchema(node, "object");
|
|
1386
|
+
if (!objectNode?.properties?.length) return node;
|
|
1387
|
+
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
|
|
1388
|
+
return createSchema({
|
|
1389
|
+
...objectNode,
|
|
1390
|
+
properties: objectNode.properties.map((prop) => {
|
|
1391
|
+
if (prop.name !== propertyName) return prop;
|
|
1392
|
+
return createProperty({
|
|
1393
|
+
...prop,
|
|
1394
|
+
schema: createSchema({
|
|
1395
|
+
type: "enum",
|
|
1396
|
+
primitive: "string",
|
|
1397
|
+
enumValues: values,
|
|
1398
|
+
name: enumName,
|
|
1399
|
+
readOnly: prop.schema.readOnly,
|
|
1400
|
+
writeOnly: prop.schema.writeOnly
|
|
1401
|
+
})
|
|
1402
|
+
});
|
|
1403
|
+
})
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Merges adjacent anonymous object members into a single anonymous object member.
|
|
1408
|
+
*
|
|
1409
|
+
* @example
|
|
1410
|
+
* ```ts
|
|
1411
|
+
* const merged = mergeAdjacentObjects([
|
|
1412
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
|
|
1413
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
|
|
1414
|
+
* ])
|
|
1415
|
+
* ```
|
|
1416
|
+
*/
|
|
1417
|
+
function mergeAdjacentObjects(members) {
|
|
1418
|
+
return members.reduce((acc, member) => {
|
|
1419
|
+
const objectMember = narrowSchema(member, "object");
|
|
1420
|
+
if (objectMember && !objectMember.name) {
|
|
1421
|
+
const previous = acc.at(-1);
|
|
1422
|
+
const previousObject = previous ? narrowSchema(previous, "object") : void 0;
|
|
1423
|
+
if (previousObject && !previousObject.name) {
|
|
1424
|
+
acc[acc.length - 1] = createSchema({
|
|
1425
|
+
...previousObject,
|
|
1426
|
+
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
1427
|
+
});
|
|
1428
|
+
return acc;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
acc.push(member);
|
|
1432
|
+
return acc;
|
|
1433
|
+
}, []);
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
1437
|
+
*
|
|
1438
|
+
* @example
|
|
1439
|
+
* ```ts
|
|
1440
|
+
* const simplified = simplifyUnion([
|
|
1441
|
+
* createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
|
|
1442
|
+
* createSchema({ type: 'string' }),
|
|
1443
|
+
* ])
|
|
1444
|
+
* // keeps only string member
|
|
1445
|
+
* ```
|
|
1446
|
+
*/
|
|
1447
|
+
function simplifyUnion(members) {
|
|
1448
|
+
const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
|
|
1449
|
+
if (!scalarPrimitives.size) return members;
|
|
1450
|
+
return members.filter((member) => {
|
|
1451
|
+
const enumNode = narrowSchema(member, "enum");
|
|
1452
|
+
if (!enumNode) return true;
|
|
1453
|
+
const primitive = enumNode.primitive;
|
|
1454
|
+
if (!primitive) return true;
|
|
1455
|
+
if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
|
|
1456
|
+
if (scalarPrimitives.has(primitive)) return false;
|
|
1457
|
+
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
1458
|
+
return true;
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
1462
|
+
const enumNode = narrowSchema(propNode, "enum");
|
|
1463
|
+
if (enumNode?.primitive === "boolean") return {
|
|
1464
|
+
...propNode,
|
|
1465
|
+
name: void 0
|
|
1466
|
+
};
|
|
1467
|
+
if (enumNode) return {
|
|
1468
|
+
...propNode,
|
|
1469
|
+
name: enumPropName(parentName, propName, enumSuffix)
|
|
1470
|
+
};
|
|
1471
|
+
return propNode;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Walks a schema tree and resolves `ref`/`enum` names through callbacks.
|
|
1475
|
+
*/
|
|
1476
|
+
function resolveNames({ node, nameMapping, resolveName, resolveEnumName }) {
|
|
1477
|
+
return transform(node, { schema(schemaNode) {
|
|
1478
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
1479
|
+
if (schemaRef && (schemaRef.ref || schemaRef.name)) {
|
|
1480
|
+
const rawRef = schemaRef.ref ?? schemaRef.name;
|
|
1481
|
+
const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
|
|
1482
|
+
if (resolved) return {
|
|
1483
|
+
...schemaNode,
|
|
1484
|
+
name: resolved
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
const schemaEnum = narrowSchema(schemaNode, "enum");
|
|
1488
|
+
if (schemaEnum?.name) {
|
|
1489
|
+
const resolved = (resolveEnumName ?? resolveName)(schemaEnum.name);
|
|
1490
|
+
if (resolved) return {
|
|
1491
|
+
...schemaNode,
|
|
1492
|
+
name: resolved
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
} });
|
|
1496
|
+
}
|
|
1497
|
+
//#endregion
|
|
1498
|
+
export { SCALAR_PRIMITIVE_TYPES, buildRefMap, caseParams, childName, collect, collectImports, composeTransformers, createDiscriminantNode, createFunctionParameter, createFunctionParameters, createObjectBindingParameter, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, defineFunctionPrinter, definePrinter, enumPropName, extractRefName, findDiscriminator, functionPrinter, httpMethods, isFunctionParameterNode, isFunctionParametersNode, isObjectBindingParameterNode, isOperationNode, isParameterNode, isPropertyNode, isResponseNode, isRootNode, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, nodeKinds, refMapToObject, resolveNames, resolveRef, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, transform, walk };
|
|
1152
1499
|
|
|
1153
1500
|
//# sourceMappingURL=index.js.map
|