@kubb/ast 5.0.0-alpha.1 → 5.0.0-alpha.11
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 +633 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +80 -2
- package/dist/index.js +625 -58
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{visitor-CmsfJzro.d.ts → visitor-D-l3dMbN.d.ts} +299 -49
- package/package.json +3 -2
- package/src/constants.ts +5 -1
- package/src/factory.ts +102 -2
- package/src/functionPrinter.ts +195 -0
- package/src/guards.ts +29 -1
- package/src/index.ts +25 -2
- package/src/mocks.ts +7 -1
- package/src/nodes/base.ts +10 -1
- package/src/nodes/function.ts +119 -0
- package/src/nodes/index.ts +4 -1
- package/src/nodes/operation.ts +4 -0
- package/src/nodes/response.ts +1 -1
- package/src/nodes/root.ts +13 -3
- package/src/nodes/schema.ts +29 -3
- package/src/printer.ts +111 -52
- package/src/types.ts +7 -1
- package/src/utils.ts +48 -0
- package/src/visitor.ts +32 -2
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
Object.defineProperty;
|
|
3
3
|
//#endregion
|
|
4
|
+
let node_util = require("node:util");
|
|
4
5
|
//#region src/constants.ts
|
|
5
6
|
const visitorDepths = {
|
|
6
7
|
shallow: "shallow",
|
|
@@ -12,7 +13,10 @@ const nodeKinds = {
|
|
|
12
13
|
schema: "Schema",
|
|
13
14
|
property: "Property",
|
|
14
15
|
parameter: "Parameter",
|
|
15
|
-
response: "Response"
|
|
16
|
+
response: "Response",
|
|
17
|
+
functionParameter: "FunctionParameter",
|
|
18
|
+
objectBindingParameter: "ObjectBindingParameter",
|
|
19
|
+
functionParameters: "FunctionParameters"
|
|
16
20
|
};
|
|
17
21
|
const schemaTypes = {
|
|
18
22
|
string: "string",
|
|
@@ -37,7 +41,8 @@ const schemaTypes = {
|
|
|
37
41
|
uuid: "uuid",
|
|
38
42
|
email: "email",
|
|
39
43
|
url: "url",
|
|
40
|
-
blob: "blob"
|
|
44
|
+
blob: "blob",
|
|
45
|
+
never: "never"
|
|
41
46
|
};
|
|
42
47
|
const httpMethods = {
|
|
43
48
|
get: "GET",
|
|
@@ -135,6 +140,288 @@ function createResponse(props) {
|
|
|
135
140
|
kind: "Response"
|
|
136
141
|
};
|
|
137
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Creates a `FunctionParameterNode`. `optional` defaults to `false`.
|
|
145
|
+
*
|
|
146
|
+
* @example Required typed param
|
|
147
|
+
* ```ts
|
|
148
|
+
* createFunctionParameter({ name: 'petId', type: 'string' })
|
|
149
|
+
* // → petId: string
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* @example Optional param
|
|
153
|
+
* ```ts
|
|
154
|
+
* createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
|
|
155
|
+
* // → params?: QueryParams
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @example Param with default (implicitly optional — cannot combine with `optional: true`)
|
|
159
|
+
* ```ts
|
|
160
|
+
* createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
|
|
161
|
+
* // → config: RequestConfig = {}
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
function createFunctionParameter(props) {
|
|
165
|
+
return {
|
|
166
|
+
optional: false,
|
|
167
|
+
...props,
|
|
168
|
+
kind: "FunctionParameter"
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Creates an `ObjectBindingParameterNode` — an object-destructured parameter group.
|
|
173
|
+
*
|
|
174
|
+
* @example Destructured object param
|
|
175
|
+
* ```ts
|
|
176
|
+
* createObjectBindingParameter({
|
|
177
|
+
* properties: [
|
|
178
|
+
* createFunctionParameter({ name: 'id', type: 'string', optional: false }),
|
|
179
|
+
* createFunctionParameter({ name: 'name', type: 'string', optional: true }),
|
|
180
|
+
* ],
|
|
181
|
+
* default: '{}',
|
|
182
|
+
* })
|
|
183
|
+
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
184
|
+
* // call → { id, name }
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* @example Inline — children emitted as individual top-level params
|
|
188
|
+
* ```ts
|
|
189
|
+
* createObjectBindingParameter({
|
|
190
|
+
* properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
|
|
191
|
+
* inline: true,
|
|
192
|
+
* })
|
|
193
|
+
* // declaration → petId: string
|
|
194
|
+
* // call → petId
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
function createObjectBindingParameter(props) {
|
|
198
|
+
return {
|
|
199
|
+
...props,
|
|
200
|
+
kind: "ObjectBindingParameter"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Creates a `FunctionParametersNode` from an ordered list of params.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* createFunctionParameters({
|
|
209
|
+
* params: [
|
|
210
|
+
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
211
|
+
* createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
|
|
212
|
+
* ],
|
|
213
|
+
* })
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
function createFunctionParameters(props = {}) {
|
|
217
|
+
return {
|
|
218
|
+
params: [],
|
|
219
|
+
...props,
|
|
220
|
+
kind: "FunctionParameters"
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/printer.ts
|
|
225
|
+
/**
|
|
226
|
+
* Creates a named printer factory. Mirrors the `createPlugin` / `createAdapter` pattern
|
|
227
|
+
* from `@kubb/core` — wraps a builder to make options optional and separates raw options
|
|
228
|
+
* from resolved options.
|
|
229
|
+
*
|
|
230
|
+
* The builder receives resolved options and returns:
|
|
231
|
+
* - `name` — a unique identifier for the printer
|
|
232
|
+
* - `options` — options stored on the returned printer instance
|
|
233
|
+
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
234
|
+
* - `print` _(optional)_ — a root-level override that becomes the public `printer.print`.
|
|
235
|
+
* Inside it, `this.print(node)` still dispatches to the `nodes` map — safe recursion, no infinite loop.
|
|
236
|
+
*
|
|
237
|
+
* When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
|
|
238
|
+
*
|
|
239
|
+
* @example Basic usage — Zod schema printer
|
|
240
|
+
* ```ts
|
|
241
|
+
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
242
|
+
*
|
|
243
|
+
* export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
|
|
244
|
+
* name: 'zod',
|
|
245
|
+
* options: { strict: options.strict ?? true },
|
|
246
|
+
* nodes: {
|
|
247
|
+
* string: () => 'z.string()',
|
|
248
|
+
* object(node) {
|
|
249
|
+
* const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
|
|
250
|
+
* return `z.object({ ${props} })`
|
|
251
|
+
* },
|
|
252
|
+
* },
|
|
253
|
+
* }))
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @example With a root-level `print` override to wrap output in a full declaration
|
|
257
|
+
* ```ts
|
|
258
|
+
* type TsPrinter = PrinterFactoryOptions<'ts', { typeName?: string }, ts.TypeNode, ts.Node>
|
|
259
|
+
*
|
|
260
|
+
* export const printerTs = definePrinter<TsPrinter>((options) => ({
|
|
261
|
+
* name: 'ts',
|
|
262
|
+
* options,
|
|
263
|
+
* nodes: { string: () => factory.keywordTypeNodes.string },
|
|
264
|
+
* print(node) {
|
|
265
|
+
* const type = this.print(node) // calls the node-level dispatcher
|
|
266
|
+
* if (!type || !this.options.typeName) return type
|
|
267
|
+
* return factory.createTypeAliasDeclaration(this.options.typeName, type)
|
|
268
|
+
* },
|
|
269
|
+
* }))
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
function definePrinter(build) {
|
|
273
|
+
return createPrinterFactory((node) => node.type)(build);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Generic printer factory. Extracts the core dispatch + context logic so it can be reused
|
|
277
|
+
* for any node type — not just `SchemaNode`. `definePrinter` is built on top of this.
|
|
278
|
+
*
|
|
279
|
+
* @param getKey — derives the handler-map key from a node. Return `undefined` to skip.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```ts
|
|
283
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
284
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
285
|
+
* )
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
function createPrinterFactory(getKey) {
|
|
289
|
+
return function(build) {
|
|
290
|
+
return (options) => {
|
|
291
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
|
|
292
|
+
const context = {
|
|
293
|
+
options: resolvedOptions,
|
|
294
|
+
print: (node) => {
|
|
295
|
+
const key = getKey(node);
|
|
296
|
+
if (key === void 0) return void 0;
|
|
297
|
+
const handler = nodes[key];
|
|
298
|
+
if (!handler) return void 0;
|
|
299
|
+
return handler.call(context, node);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
name,
|
|
304
|
+
options: resolvedOptions,
|
|
305
|
+
print: printOverride ? printOverride.bind(context) : context.print
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/functionPrinter.ts
|
|
312
|
+
const kindToHandlerKey = {
|
|
313
|
+
FunctionParameter: "functionParameter",
|
|
314
|
+
ObjectBindingParameter: "objectBindingParameter",
|
|
315
|
+
FunctionParameters: "functionParameters"
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Creates a named function-signature printer factory.
|
|
319
|
+
* Built on `createPrinterFactory` — dispatches on `node.kind` instead of `node.type`.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
|
|
324
|
+
*
|
|
325
|
+
* export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
|
|
326
|
+
* name: 'my',
|
|
327
|
+
* options,
|
|
328
|
+
* nodes: {
|
|
329
|
+
* functionParameter(node) {
|
|
330
|
+
* return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
|
|
331
|
+
* },
|
|
332
|
+
* objectBindingParameter(node) {
|
|
333
|
+
* const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
|
|
334
|
+
* return `{ ${inner} }`
|
|
335
|
+
* },
|
|
336
|
+
* functionParameters(node) {
|
|
337
|
+
* return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
|
|
338
|
+
* },
|
|
339
|
+
* },
|
|
340
|
+
* }))
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
|
|
344
|
+
function rank(param) {
|
|
345
|
+
if (param.kind === "ObjectBindingParameter") {
|
|
346
|
+
if (param.default) return 2;
|
|
347
|
+
return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
|
|
348
|
+
}
|
|
349
|
+
if (param.rest) return 3;
|
|
350
|
+
if (param.default) return 2;
|
|
351
|
+
return param.optional ? 1 : 0;
|
|
352
|
+
}
|
|
353
|
+
function sortParams(params) {
|
|
354
|
+
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
355
|
+
}
|
|
356
|
+
function sortChildParams(params) {
|
|
357
|
+
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Default function-signature printer. Covers the four standard output modes
|
|
361
|
+
* used throughout Kubb plugins.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* const printer = functionSignaturePrinter({ mode: 'declaration' })
|
|
366
|
+
*
|
|
367
|
+
* const sig = createFunctionParameters({
|
|
368
|
+
* params: [
|
|
369
|
+
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
370
|
+
* createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
|
|
371
|
+
* ],
|
|
372
|
+
* })
|
|
373
|
+
*
|
|
374
|
+
* printer.print(sig) // → "petId: string, config: Config = {}"
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
const functionPrinter = defineFunctionPrinter((options) => ({
|
|
378
|
+
name: "functionParameters",
|
|
379
|
+
options,
|
|
380
|
+
nodes: {
|
|
381
|
+
functionParameter(node) {
|
|
382
|
+
const { mode, transformName, transformType } = this.options;
|
|
383
|
+
const name = transformName ? transformName(node.name) : node.name;
|
|
384
|
+
const type = node.type && transformType ? transformType(node.type) : node.type;
|
|
385
|
+
if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
|
|
386
|
+
if (mode === "call") return node.rest ? `...${name}` : name;
|
|
387
|
+
if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
|
|
388
|
+
if (type) {
|
|
389
|
+
if (node.optional) return `${name}?: ${type}`;
|
|
390
|
+
return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
|
|
391
|
+
}
|
|
392
|
+
return node.default ? `${name} = ${node.default}` : name;
|
|
393
|
+
},
|
|
394
|
+
objectBindingParameter(node) {
|
|
395
|
+
const { mode, transformName, transformType } = this.options;
|
|
396
|
+
const sorted = sortChildParams(node.properties);
|
|
397
|
+
const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
|
|
398
|
+
if (node.inline) return sorted.map((p) => this.print(p)).filter(Boolean).join(", ");
|
|
399
|
+
if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
400
|
+
if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
401
|
+
const names = sorted.map((p) => {
|
|
402
|
+
return transformName ? transformName(p.name) : p.name;
|
|
403
|
+
});
|
|
404
|
+
const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
|
|
405
|
+
if (!nameStr) return null;
|
|
406
|
+
let typeAnnotation = node.type;
|
|
407
|
+
if (!typeAnnotation) {
|
|
408
|
+
const typeParts = sorted.filter((p) => p.type).map((p) => {
|
|
409
|
+
const t = transformType && p.type ? transformType(p.type) : p.type;
|
|
410
|
+
return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
|
|
411
|
+
});
|
|
412
|
+
typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
|
|
413
|
+
}
|
|
414
|
+
if (typeAnnotation) {
|
|
415
|
+
if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
|
|
416
|
+
return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
|
|
417
|
+
}
|
|
418
|
+
return node.default ? `${nameStr} = ${node.default}` : nameStr;
|
|
419
|
+
},
|
|
420
|
+
functionParameters(node) {
|
|
421
|
+
return sortParams(node.params).map((p) => this.print(p)).filter(Boolean).join(", ");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}));
|
|
138
425
|
//#endregion
|
|
139
426
|
//#region src/guards.ts
|
|
140
427
|
/**
|
|
@@ -170,60 +457,18 @@ const isParameterNode = isKind("Parameter");
|
|
|
170
457
|
* Type guard for `ResponseNode`.
|
|
171
458
|
*/
|
|
172
459
|
const isResponseNode = isKind("Response");
|
|
173
|
-
//#endregion
|
|
174
|
-
//#region src/printer.ts
|
|
175
460
|
/**
|
|
176
|
-
*
|
|
177
|
-
* from `@kubb/core` — wraps a builder to make options optional and separates raw options
|
|
178
|
-
* from resolved options.
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* ```ts
|
|
182
|
-
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, { strict: boolean }, string>
|
|
183
|
-
*
|
|
184
|
-
* export const zodPrinter = definePrinter<ZodPrinter>((options) => {
|
|
185
|
-
* const { strict = true } = options
|
|
186
|
-
* return {
|
|
187
|
-
* name: 'zod',
|
|
188
|
-
* options: { strict },
|
|
189
|
-
* nodes: {
|
|
190
|
-
* string(node) {
|
|
191
|
-
* return `z.string()`
|
|
192
|
-
* },
|
|
193
|
-
* object(node) {
|
|
194
|
-
* const props = node.properties
|
|
195
|
-
* ?.map(p => `${p.name}: ${this.print(p)}`)
|
|
196
|
-
* .join(', ') ?? ''
|
|
197
|
-
* return `z.object({ ${props} })`
|
|
198
|
-
* },
|
|
199
|
-
* },
|
|
200
|
-
* }
|
|
201
|
-
* })
|
|
202
|
-
*
|
|
203
|
-
* const printer = zodPrinter({ strict: false })
|
|
204
|
-
* printer.name // 'zod'
|
|
205
|
-
* printer.options // { strict: false }
|
|
206
|
-
* printer.print(node) // 'z.string()'
|
|
207
|
-
* ```
|
|
461
|
+
* Type guard for `FunctionParameterNode`.
|
|
208
462
|
*/
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
};
|
|
219
|
-
return {
|
|
220
|
-
name,
|
|
221
|
-
options: resolvedOptions,
|
|
222
|
-
print: context.print,
|
|
223
|
-
for: (nodes) => nodes.map(context.print)
|
|
224
|
-
};
|
|
225
|
-
};
|
|
226
|
-
}
|
|
463
|
+
const isFunctionParameterNode = isKind("FunctionParameter");
|
|
464
|
+
/**
|
|
465
|
+
* Type guard for `ObjectBindingParameterNode`.
|
|
466
|
+
*/
|
|
467
|
+
const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
|
|
468
|
+
/**
|
|
469
|
+
* Type guard for `FunctionParametersNode`.
|
|
470
|
+
*/
|
|
471
|
+
const isFunctionParametersNode = isKind("FunctionParameters");
|
|
227
472
|
//#endregion
|
|
228
473
|
//#region src/refs.ts
|
|
229
474
|
/**
|
|
@@ -247,7 +492,309 @@ function refMapToObject(refMap) {
|
|
|
247
492
|
return Object.fromEntries(refMap);
|
|
248
493
|
}
|
|
249
494
|
//#endregion
|
|
495
|
+
//#region ../../internals/utils/dist/index.js
|
|
496
|
+
/**
|
|
497
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
498
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
499
|
+
* and capitalizes each word according to `pascal`.
|
|
500
|
+
*
|
|
501
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
502
|
+
*/
|
|
503
|
+
function toCamelOrPascal(text, pascal) {
|
|
504
|
+
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) => {
|
|
505
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
506
|
+
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
507
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
508
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
512
|
+
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
513
|
+
* Segments are joined with `/` to form a file path.
|
|
514
|
+
*/
|
|
515
|
+
function applyToFileParts(text, transformPart) {
|
|
516
|
+
const parts = text.split(".");
|
|
517
|
+
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Converts `text` to camelCase.
|
|
521
|
+
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
522
|
+
*
|
|
523
|
+
* @example
|
|
524
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
525
|
+
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
526
|
+
*/
|
|
527
|
+
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
528
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
529
|
+
prefix,
|
|
530
|
+
suffix
|
|
531
|
+
} : {}));
|
|
532
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
533
|
+
}
|
|
534
|
+
/** Returns a `CLIAdapter` with type inference. Pass a different adapter to `createCLI` to swap the CLI engine. */
|
|
535
|
+
function defineCLIAdapter(adapter) {
|
|
536
|
+
return adapter;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Serializes `CommandDefinition[]` to a plain, JSON-serializable structure.
|
|
540
|
+
* Use to expose CLI capabilities to AI agents or MCP tools.
|
|
541
|
+
*/
|
|
542
|
+
function getCommandSchema(defs) {
|
|
543
|
+
return defs.map(serializeCommand);
|
|
544
|
+
}
|
|
545
|
+
function serializeCommand(def) {
|
|
546
|
+
return {
|
|
547
|
+
name: def.name,
|
|
548
|
+
description: def.description,
|
|
549
|
+
arguments: def.arguments,
|
|
550
|
+
options: serializeOptions(def.options ?? {}),
|
|
551
|
+
subCommands: def.subCommands ? def.subCommands.map(serializeCommand) : []
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function serializeOptions(options) {
|
|
555
|
+
return Object.entries(options).map(([name, opt]) => {
|
|
556
|
+
return {
|
|
557
|
+
name,
|
|
558
|
+
flags: `${opt.short ? `-${opt.short}, ` : ""}--${name}${opt.type === "string" ? ` <${opt.hint ?? name}>` : ""}`,
|
|
559
|
+
type: opt.type,
|
|
560
|
+
description: opt.description,
|
|
561
|
+
...opt.default !== void 0 ? { default: opt.default } : {},
|
|
562
|
+
...opt.hint ? { hint: opt.hint } : {},
|
|
563
|
+
...opt.enum ? { enum: opt.enum } : {},
|
|
564
|
+
...opt.required ? { required: opt.required } : {}
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
/** Prints formatted help output for a command using its `CommandDefinition`. */
|
|
569
|
+
function renderHelp(def, parentName) {
|
|
570
|
+
const schema = getCommandSchema([def])[0];
|
|
571
|
+
const programName = parentName ? `${parentName} ${schema.name}` : schema.name;
|
|
572
|
+
const argsPart = schema.arguments?.length ? ` ${schema.arguments.join(" ")}` : "";
|
|
573
|
+
const subCmdPart = schema.subCommands.length ? " <command>" : "";
|
|
574
|
+
console.log(`\n${(0, node_util.styleText)("bold", "Usage:")} ${programName}${argsPart}${subCmdPart} [options]\n`);
|
|
575
|
+
if (schema.description) console.log(` ${schema.description}\n`);
|
|
576
|
+
if (schema.subCommands.length) {
|
|
577
|
+
console.log((0, node_util.styleText)("bold", "Commands:"));
|
|
578
|
+
for (const sub of schema.subCommands) console.log(` ${(0, node_util.styleText)("cyan", sub.name.padEnd(16))}${sub.description}`);
|
|
579
|
+
console.log();
|
|
580
|
+
}
|
|
581
|
+
const options = [...schema.options, {
|
|
582
|
+
name: "help",
|
|
583
|
+
flags: "-h, --help",
|
|
584
|
+
type: "boolean",
|
|
585
|
+
description: "Show help"
|
|
586
|
+
}];
|
|
587
|
+
console.log((0, node_util.styleText)("bold", "Options:"));
|
|
588
|
+
for (const opt of options) {
|
|
589
|
+
const flags = (0, node_util.styleText)("cyan", opt.flags.padEnd(30));
|
|
590
|
+
const defaultPart = opt.default !== void 0 ? (0, node_util.styleText)("dim", ` (default: ${opt.default})`) : "";
|
|
591
|
+
console.log(` ${flags}${opt.description}${defaultPart}`);
|
|
592
|
+
}
|
|
593
|
+
console.log();
|
|
594
|
+
}
|
|
595
|
+
function buildParseOptions(def) {
|
|
596
|
+
const result = { help: {
|
|
597
|
+
type: "boolean",
|
|
598
|
+
short: "h"
|
|
599
|
+
} };
|
|
600
|
+
for (const [name, opt] of Object.entries(def.options ?? {})) result[name] = {
|
|
601
|
+
type: opt.type,
|
|
602
|
+
...opt.short ? { short: opt.short } : {},
|
|
603
|
+
...opt.default !== void 0 ? { default: opt.default } : {}
|
|
604
|
+
};
|
|
605
|
+
return result;
|
|
606
|
+
}
|
|
607
|
+
async function runCommand(def, argv, parentName) {
|
|
608
|
+
const parseOptions = buildParseOptions(def);
|
|
609
|
+
let parsed;
|
|
610
|
+
try {
|
|
611
|
+
const result = (0, node_util.parseArgs)({
|
|
612
|
+
args: argv,
|
|
613
|
+
options: parseOptions,
|
|
614
|
+
allowPositionals: true,
|
|
615
|
+
strict: false
|
|
616
|
+
});
|
|
617
|
+
parsed = {
|
|
618
|
+
values: result.values,
|
|
619
|
+
positionals: result.positionals
|
|
620
|
+
};
|
|
621
|
+
} catch {
|
|
622
|
+
renderHelp(def, parentName);
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
if (parsed.values["help"]) {
|
|
626
|
+
renderHelp(def, parentName);
|
|
627
|
+
process.exit(0);
|
|
628
|
+
}
|
|
629
|
+
for (const [name, opt] of Object.entries(def.options ?? {})) if (opt.required && parsed.values[name] === void 0) {
|
|
630
|
+
console.error((0, node_util.styleText)("red", `Error: --${name} is required`));
|
|
631
|
+
renderHelp(def, parentName);
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
if (!def.run) {
|
|
635
|
+
renderHelp(def, parentName);
|
|
636
|
+
process.exit(0);
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
await def.run(parsed);
|
|
640
|
+
} catch (err) {
|
|
641
|
+
console.error((0, node_util.styleText)("red", `Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
642
|
+
renderHelp(def, parentName);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function printRootHelp(programName, version, defs) {
|
|
647
|
+
console.log(`\n${(0, node_util.styleText)("bold", "Usage:")} ${programName} <command> [options]\n`);
|
|
648
|
+
console.log(` Kubb generation — v${version}\n`);
|
|
649
|
+
console.log((0, node_util.styleText)("bold", "Commands:"));
|
|
650
|
+
for (const def of defs) console.log(` ${(0, node_util.styleText)("cyan", def.name.padEnd(16))}${def.description}`);
|
|
651
|
+
console.log();
|
|
652
|
+
console.log((0, node_util.styleText)("bold", "Options:"));
|
|
653
|
+
console.log(` ${(0, node_util.styleText)("cyan", "-v, --version".padEnd(30))}Show version number`);
|
|
654
|
+
console.log(` ${(0, node_util.styleText)("cyan", "-h, --help".padEnd(30))}Show help`);
|
|
655
|
+
console.log();
|
|
656
|
+
console.log(`Run ${(0, node_util.styleText)("cyan", `${programName} <command> --help`)} for command-specific help.\n`);
|
|
657
|
+
}
|
|
658
|
+
defineCLIAdapter({
|
|
659
|
+
renderHelp(def, parentName) {
|
|
660
|
+
renderHelp(def, parentName);
|
|
661
|
+
},
|
|
662
|
+
async run(defs, argv, opts) {
|
|
663
|
+
const { programName, defaultCommandName, version } = opts;
|
|
664
|
+
const args = argv.length >= 2 && argv[0]?.includes("node") ? argv.slice(2) : argv;
|
|
665
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
666
|
+
console.log(version);
|
|
667
|
+
process.exit(0);
|
|
668
|
+
}
|
|
669
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
670
|
+
printRootHelp(programName, version, defs);
|
|
671
|
+
process.exit(0);
|
|
672
|
+
}
|
|
673
|
+
if (args.length === 0) {
|
|
674
|
+
const defaultDef = defs.find((d) => d.name === defaultCommandName);
|
|
675
|
+
if (defaultDef?.run) await runCommand(defaultDef, [], programName);
|
|
676
|
+
else printRootHelp(programName, version, defs);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const [first, ...rest] = args;
|
|
680
|
+
const isKnownSubcommand = defs.some((d) => d.name === first);
|
|
681
|
+
let def;
|
|
682
|
+
let commandArgv;
|
|
683
|
+
let parentName;
|
|
684
|
+
if (isKnownSubcommand) {
|
|
685
|
+
def = defs.find((d) => d.name === first);
|
|
686
|
+
commandArgv = rest;
|
|
687
|
+
parentName = programName;
|
|
688
|
+
} else {
|
|
689
|
+
def = defs.find((d) => d.name === defaultCommandName);
|
|
690
|
+
commandArgv = args;
|
|
691
|
+
parentName = programName;
|
|
692
|
+
}
|
|
693
|
+
if (!def) {
|
|
694
|
+
console.error(`Unknown command: ${first}`);
|
|
695
|
+
printRootHelp(programName, version, defs);
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
if (def.subCommands?.length) {
|
|
699
|
+
const [subName, ...subRest] = commandArgv;
|
|
700
|
+
const subDef = def.subCommands.find((s) => s.name === subName);
|
|
701
|
+
if (subName === "--help" || subName === "-h") {
|
|
702
|
+
renderHelp(def, parentName);
|
|
703
|
+
process.exit(0);
|
|
704
|
+
}
|
|
705
|
+
if (!subDef) {
|
|
706
|
+
renderHelp(def, parentName);
|
|
707
|
+
process.exit(subName ? 1 : 0);
|
|
708
|
+
}
|
|
709
|
+
await runCommand(subDef, subRest, `${parentName} ${def.name}`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
await runCommand(def, commandArgv, parentName);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
/**
|
|
716
|
+
* Parses a CSS hex color string (`#RGB`) into its RGB channels.
|
|
717
|
+
* Falls back to `255` for any channel that cannot be parsed.
|
|
718
|
+
*/
|
|
719
|
+
function parseHex(color) {
|
|
720
|
+
const int = Number.parseInt(color.replace("#", ""), 16);
|
|
721
|
+
return Number.isNaN(int) ? {
|
|
722
|
+
r: 255,
|
|
723
|
+
g: 255,
|
|
724
|
+
b: 255
|
|
725
|
+
} : {
|
|
726
|
+
r: int >> 16 & 255,
|
|
727
|
+
g: int >> 8 & 255,
|
|
728
|
+
b: int & 255
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Returns a function that wraps a string in a 24-bit ANSI true-color escape sequence
|
|
733
|
+
* for the given hex color.
|
|
734
|
+
*/
|
|
735
|
+
function hex(color) {
|
|
736
|
+
const { r, g, b } = parseHex(color);
|
|
737
|
+
return (text) => `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
|
|
738
|
+
}
|
|
739
|
+
hex("#F55A17"), hex("#F5A217"), hex("#F58517"), hex("#B45309"), hex("#FFFFFF"), hex("#adadc6"), hex("#FDA4AF");
|
|
740
|
+
/**
|
|
741
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
742
|
+
*/
|
|
743
|
+
function isValidVarName(name) {
|
|
744
|
+
try {
|
|
745
|
+
new Function(`var ${name}`);
|
|
746
|
+
} catch {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
//#endregion
|
|
752
|
+
//#region src/utils.ts
|
|
753
|
+
const plainStringTypes = new Set([
|
|
754
|
+
"string",
|
|
755
|
+
"uuid",
|
|
756
|
+
"email",
|
|
757
|
+
"url",
|
|
758
|
+
"datetime"
|
|
759
|
+
]);
|
|
760
|
+
/**
|
|
761
|
+
* Returns `true` when a schema node will be represented as a plain string in generated code.
|
|
762
|
+
*
|
|
763
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
764
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
765
|
+
*/
|
|
766
|
+
function isPlainStringType(node) {
|
|
767
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
768
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
769
|
+
if (temporal) return temporal.representation !== "date";
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Transforms the `name` field of each parameter node according to the given casing strategy.
|
|
774
|
+
*
|
|
775
|
+
* The original `params` array is never mutated — a new array of cloned nodes is returned.
|
|
776
|
+
* When no `casing` is provided the original array is returned as-is.
|
|
777
|
+
*
|
|
778
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
779
|
+
* in the generated output match the desired casing while the original
|
|
780
|
+
* `OperationNode.parameters` array remains untouched for other consumers.
|
|
781
|
+
*/
|
|
782
|
+
function applyParamsCasing(params, casing) {
|
|
783
|
+
if (!casing) return params;
|
|
784
|
+
return params.map((param) => {
|
|
785
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
786
|
+
return {
|
|
787
|
+
...param,
|
|
788
|
+
name: transformed
|
|
789
|
+
};
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
//#endregion
|
|
250
793
|
//#region src/visitor.ts
|
|
794
|
+
/**
|
|
795
|
+
* Creates a concurrency-limiting wrapper. At most `concurrency` promises may be
|
|
796
|
+
* in-flight simultaneously; additional calls are queued and dispatched as slots free.
|
|
797
|
+
*/
|
|
251
798
|
function createLimit(concurrency) {
|
|
252
799
|
let active = 0;
|
|
253
800
|
const queue = [];
|
|
@@ -270,7 +817,10 @@ function createLimit(concurrency) {
|
|
|
270
817
|
};
|
|
271
818
|
}
|
|
272
819
|
/**
|
|
273
|
-
*
|
|
820
|
+
* Returns the immediate traversable children of `node`.
|
|
821
|
+
*
|
|
822
|
+
* For `Schema` nodes, children (properties, items, members) are only included
|
|
823
|
+
* when `recurse` is `true`; shallow traversal omits them entirely.
|
|
274
824
|
*/
|
|
275
825
|
function getChildren(node, recurse) {
|
|
276
826
|
switch (node.kind) {
|
|
@@ -286,11 +836,15 @@ function getChildren(node, recurse) {
|
|
|
286
836
|
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
287
837
|
if ("items" in node && node.items) children.push(...node.items);
|
|
288
838
|
if ("members" in node && node.members) children.push(...node.members);
|
|
839
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
289
840
|
return children;
|
|
290
841
|
}
|
|
291
842
|
case "Property": return [node.schema];
|
|
292
843
|
case "Parameter": return [node.schema];
|
|
293
844
|
case "Response": return node.schema ? [node.schema] : [];
|
|
845
|
+
case "FunctionParameter":
|
|
846
|
+
case "ObjectBindingParameter":
|
|
847
|
+
case "FunctionParameters": return [];
|
|
294
848
|
}
|
|
295
849
|
}
|
|
296
850
|
/**
|
|
@@ -300,6 +854,9 @@ function getChildren(node, recurse) {
|
|
|
300
854
|
async function walk(node, visitor, options = {}) {
|
|
301
855
|
return _walk(node, visitor, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30));
|
|
302
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* Internal recursive walk implementation — calls visitor then recurses into children.
|
|
859
|
+
*/
|
|
303
860
|
async function _walk(node, visitor, recurse, limit) {
|
|
304
861
|
switch (node.kind) {
|
|
305
862
|
case "Root":
|
|
@@ -320,6 +877,9 @@ async function _walk(node, visitor, recurse, limit) {
|
|
|
320
877
|
case "Response":
|
|
321
878
|
await limit(() => visitor.response?.(node));
|
|
322
879
|
break;
|
|
880
|
+
case "FunctionParameter":
|
|
881
|
+
case "ObjectBindingParameter":
|
|
882
|
+
case "FunctionParameters": break;
|
|
323
883
|
}
|
|
324
884
|
const children = getChildren(node, recurse);
|
|
325
885
|
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
|
|
@@ -356,7 +916,8 @@ function transform(node, visitor, options = {}) {
|
|
|
356
916
|
...schema,
|
|
357
917
|
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, visitor, options)) } : {},
|
|
358
918
|
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, visitor, options)) } : {},
|
|
359
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {}
|
|
919
|
+
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {},
|
|
920
|
+
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, visitor, options) } : {}
|
|
360
921
|
};
|
|
361
922
|
}
|
|
362
923
|
case "Property": {
|
|
@@ -383,9 +944,12 @@ function transform(node, visitor, options = {}) {
|
|
|
383
944
|
if (replaced) response = replaced;
|
|
384
945
|
return {
|
|
385
946
|
...response,
|
|
386
|
-
schema:
|
|
947
|
+
schema: transform(response.schema, visitor, options)
|
|
387
948
|
};
|
|
388
949
|
}
|
|
950
|
+
case "FunctionParameter":
|
|
951
|
+
case "ObjectBindingParameter":
|
|
952
|
+
case "FunctionParameters": return node;
|
|
389
953
|
}
|
|
390
954
|
}
|
|
391
955
|
/**
|
|
@@ -414,14 +978,21 @@ function collect(node, visitor, options = {}) {
|
|
|
414
978
|
case "Response":
|
|
415
979
|
v = visitor.response?.(node);
|
|
416
980
|
break;
|
|
981
|
+
case "FunctionParameter":
|
|
982
|
+
case "ObjectBindingParameter":
|
|
983
|
+
case "FunctionParameters": break;
|
|
417
984
|
}
|
|
418
985
|
if (v !== void 0) results.push(v);
|
|
419
986
|
for (const child of getChildren(node, recurse)) for (const item of collect(child, visitor, options)) results.push(item);
|
|
420
987
|
return results;
|
|
421
988
|
}
|
|
422
989
|
//#endregion
|
|
990
|
+
exports.applyParamsCasing = applyParamsCasing;
|
|
423
991
|
exports.buildRefMap = buildRefMap;
|
|
424
992
|
exports.collect = collect;
|
|
993
|
+
exports.createFunctionParameter = createFunctionParameter;
|
|
994
|
+
exports.createFunctionParameters = createFunctionParameters;
|
|
995
|
+
exports.createObjectBindingParameter = createObjectBindingParameter;
|
|
425
996
|
exports.createOperation = createOperation;
|
|
426
997
|
exports.createParameter = createParameter;
|
|
427
998
|
exports.createProperty = createProperty;
|
|
@@ -429,9 +1000,14 @@ exports.createResponse = createResponse;
|
|
|
429
1000
|
exports.createRoot = createRoot;
|
|
430
1001
|
exports.createSchema = createSchema;
|
|
431
1002
|
exports.definePrinter = definePrinter;
|
|
1003
|
+
exports.functionPrinter = functionPrinter;
|
|
432
1004
|
exports.httpMethods = httpMethods;
|
|
1005
|
+
exports.isFunctionParameterNode = isFunctionParameterNode;
|
|
1006
|
+
exports.isFunctionParametersNode = isFunctionParametersNode;
|
|
1007
|
+
exports.isObjectBindingParameterNode = isObjectBindingParameterNode;
|
|
433
1008
|
exports.isOperationNode = isOperationNode;
|
|
434
1009
|
exports.isParameterNode = isParameterNode;
|
|
1010
|
+
exports.isPlainStringType = isPlainStringType;
|
|
435
1011
|
exports.isPropertyNode = isPropertyNode;
|
|
436
1012
|
exports.isResponseNode = isResponseNode;
|
|
437
1013
|
exports.isRootNode = isRootNode;
|