@kubb/ast 5.0.0-alpha.1 → 5.0.0-alpha.10
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 +630 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +80 -2
- package/dist/index.js +622 -57
- 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 +28 -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) {
|
|
@@ -291,6 +841,9 @@ function getChildren(node, recurse) {
|
|
|
291
841
|
case "Property": return [node.schema];
|
|
292
842
|
case "Parameter": return [node.schema];
|
|
293
843
|
case "Response": return node.schema ? [node.schema] : [];
|
|
844
|
+
case "FunctionParameter":
|
|
845
|
+
case "ObjectBindingParameter":
|
|
846
|
+
case "FunctionParameters": return [];
|
|
294
847
|
}
|
|
295
848
|
}
|
|
296
849
|
/**
|
|
@@ -300,6 +853,9 @@ function getChildren(node, recurse) {
|
|
|
300
853
|
async function walk(node, visitor, options = {}) {
|
|
301
854
|
return _walk(node, visitor, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30));
|
|
302
855
|
}
|
|
856
|
+
/**
|
|
857
|
+
* Internal recursive walk implementation — calls visitor then recurses into children.
|
|
858
|
+
*/
|
|
303
859
|
async function _walk(node, visitor, recurse, limit) {
|
|
304
860
|
switch (node.kind) {
|
|
305
861
|
case "Root":
|
|
@@ -320,6 +876,9 @@ async function _walk(node, visitor, recurse, limit) {
|
|
|
320
876
|
case "Response":
|
|
321
877
|
await limit(() => visitor.response?.(node));
|
|
322
878
|
break;
|
|
879
|
+
case "FunctionParameter":
|
|
880
|
+
case "ObjectBindingParameter":
|
|
881
|
+
case "FunctionParameters": break;
|
|
323
882
|
}
|
|
324
883
|
const children = getChildren(node, recurse);
|
|
325
884
|
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
|
|
@@ -383,9 +942,12 @@ function transform(node, visitor, options = {}) {
|
|
|
383
942
|
if (replaced) response = replaced;
|
|
384
943
|
return {
|
|
385
944
|
...response,
|
|
386
|
-
schema:
|
|
945
|
+
schema: transform(response.schema, visitor, options)
|
|
387
946
|
};
|
|
388
947
|
}
|
|
948
|
+
case "FunctionParameter":
|
|
949
|
+
case "ObjectBindingParameter":
|
|
950
|
+
case "FunctionParameters": return node;
|
|
389
951
|
}
|
|
390
952
|
}
|
|
391
953
|
/**
|
|
@@ -414,14 +976,21 @@ function collect(node, visitor, options = {}) {
|
|
|
414
976
|
case "Response":
|
|
415
977
|
v = visitor.response?.(node);
|
|
416
978
|
break;
|
|
979
|
+
case "FunctionParameter":
|
|
980
|
+
case "ObjectBindingParameter":
|
|
981
|
+
case "FunctionParameters": break;
|
|
417
982
|
}
|
|
418
983
|
if (v !== void 0) results.push(v);
|
|
419
984
|
for (const child of getChildren(node, recurse)) for (const item of collect(child, visitor, options)) results.push(item);
|
|
420
985
|
return results;
|
|
421
986
|
}
|
|
422
987
|
//#endregion
|
|
988
|
+
exports.applyParamsCasing = applyParamsCasing;
|
|
423
989
|
exports.buildRefMap = buildRefMap;
|
|
424
990
|
exports.collect = collect;
|
|
991
|
+
exports.createFunctionParameter = createFunctionParameter;
|
|
992
|
+
exports.createFunctionParameters = createFunctionParameters;
|
|
993
|
+
exports.createObjectBindingParameter = createObjectBindingParameter;
|
|
425
994
|
exports.createOperation = createOperation;
|
|
426
995
|
exports.createParameter = createParameter;
|
|
427
996
|
exports.createProperty = createProperty;
|
|
@@ -429,9 +998,14 @@ exports.createResponse = createResponse;
|
|
|
429
998
|
exports.createRoot = createRoot;
|
|
430
999
|
exports.createSchema = createSchema;
|
|
431
1000
|
exports.definePrinter = definePrinter;
|
|
1001
|
+
exports.functionPrinter = functionPrinter;
|
|
432
1002
|
exports.httpMethods = httpMethods;
|
|
1003
|
+
exports.isFunctionParameterNode = isFunctionParameterNode;
|
|
1004
|
+
exports.isFunctionParametersNode = isFunctionParametersNode;
|
|
1005
|
+
exports.isObjectBindingParameterNode = isObjectBindingParameterNode;
|
|
433
1006
|
exports.isOperationNode = isOperationNode;
|
|
434
1007
|
exports.isParameterNode = isParameterNode;
|
|
1008
|
+
exports.isPlainStringType = isPlainStringType;
|
|
435
1009
|
exports.isPropertyNode = isPropertyNode;
|
|
436
1010
|
exports.isResponseNode = isResponseNode;
|
|
437
1011
|
exports.isRootNode = isRootNode;
|