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