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