@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.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) {
|
|
@@ -284,11 +834,15 @@ function getChildren(node, recurse) {
|
|
|
284
834
|
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
285
835
|
if ("items" in node && node.items) children.push(...node.items);
|
|
286
836
|
if ("members" in node && node.members) children.push(...node.members);
|
|
837
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
287
838
|
return children;
|
|
288
839
|
}
|
|
289
840
|
case "Property": return [node.schema];
|
|
290
841
|
case "Parameter": return [node.schema];
|
|
291
842
|
case "Response": return node.schema ? [node.schema] : [];
|
|
843
|
+
case "FunctionParameter":
|
|
844
|
+
case "ObjectBindingParameter":
|
|
845
|
+
case "FunctionParameters": return [];
|
|
292
846
|
}
|
|
293
847
|
}
|
|
294
848
|
/**
|
|
@@ -298,6 +852,9 @@ function getChildren(node, recurse) {
|
|
|
298
852
|
async function walk(node, visitor, options = {}) {
|
|
299
853
|
return _walk(node, visitor, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30));
|
|
300
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Internal recursive walk implementation — calls visitor then recurses into children.
|
|
857
|
+
*/
|
|
301
858
|
async function _walk(node, visitor, recurse, limit) {
|
|
302
859
|
switch (node.kind) {
|
|
303
860
|
case "Root":
|
|
@@ -318,6 +875,9 @@ async function _walk(node, visitor, recurse, limit) {
|
|
|
318
875
|
case "Response":
|
|
319
876
|
await limit(() => visitor.response?.(node));
|
|
320
877
|
break;
|
|
878
|
+
case "FunctionParameter":
|
|
879
|
+
case "ObjectBindingParameter":
|
|
880
|
+
case "FunctionParameters": break;
|
|
321
881
|
}
|
|
322
882
|
const children = getChildren(node, recurse);
|
|
323
883
|
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
|
|
@@ -354,7 +914,8 @@ function transform(node, visitor, options = {}) {
|
|
|
354
914
|
...schema,
|
|
355
915
|
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, visitor, options)) } : {},
|
|
356
916
|
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, visitor, options)) } : {},
|
|
357
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {}
|
|
917
|
+
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {},
|
|
918
|
+
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, visitor, options) } : {}
|
|
358
919
|
};
|
|
359
920
|
}
|
|
360
921
|
case "Property": {
|
|
@@ -381,9 +942,12 @@ function transform(node, visitor, options = {}) {
|
|
|
381
942
|
if (replaced) response = replaced;
|
|
382
943
|
return {
|
|
383
944
|
...response,
|
|
384
|
-
schema:
|
|
945
|
+
schema: transform(response.schema, visitor, options)
|
|
385
946
|
};
|
|
386
947
|
}
|
|
948
|
+
case "FunctionParameter":
|
|
949
|
+
case "ObjectBindingParameter":
|
|
950
|
+
case "FunctionParameters": return node;
|
|
387
951
|
}
|
|
388
952
|
}
|
|
389
953
|
/**
|
|
@@ -412,12 +976,15 @@ function collect(node, visitor, options = {}) {
|
|
|
412
976
|
case "Response":
|
|
413
977
|
v = visitor.response?.(node);
|
|
414
978
|
break;
|
|
979
|
+
case "FunctionParameter":
|
|
980
|
+
case "ObjectBindingParameter":
|
|
981
|
+
case "FunctionParameters": break;
|
|
415
982
|
}
|
|
416
983
|
if (v !== void 0) results.push(v);
|
|
417
984
|
for (const child of getChildren(node, recurse)) for (const item of collect(child, visitor, options)) results.push(item);
|
|
418
985
|
return results;
|
|
419
986
|
}
|
|
420
987
|
//#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 };
|
|
988
|
+
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
989
|
|
|
423
990
|
//# sourceMappingURL=index.js.map
|