@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 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
- * Creates a named printer factory. Mirrors the `definePlugin` / `defineAdapter` pattern
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
- function definePrinter(build) {
210
- return (options) => {
211
- const { name, options: resolvedOptions, nodes } = build(options ?? {});
212
- const context = {
213
- options: resolvedOptions,
214
- print: (node) => {
215
- const handler = nodes[node.type];
216
- return handler ? handler.call(context, node) : void 0;
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
- * Traversable children of `node`, respecting `recurse` for schema nodes.
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: response.schema ? transform(response.schema, visitor, options) : void 0
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;