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