@codewithagents/openapi-server 1.7.0 → 1.9.0

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/cli.cjs CHANGED
@@ -18475,44 +18475,46 @@ function validateInputPath(resolvedInput) {
18475
18475
  }
18476
18476
  }
18477
18477
  }
18478
- async function loadConfigFile(opts) {
18479
- const resolvedConfigPath = opts.configPath ?? (0, import_node_path.join)(opts.cwd, opts.defaultFileName);
18480
- if (opts.configPath !== void 0) {
18481
- validateConfigPath(opts.configPath);
18478
+ async function loadJsConfig(resolvedConfigPath) {
18479
+ let mod;
18480
+ try {
18481
+ mod = await import((0, import_node_url.pathToFileURL)(resolvedConfigPath).href);
18482
+ } catch (err) {
18483
+ const message = err instanceof Error ? err.message : String(err);
18484
+ throw new Error(`Failed to load JS config file: ${resolvedConfigPath}
18485
+ ${message}`);
18486
+ }
18487
+ const exported = mod["default"] ?? mod;
18488
+ if (typeof exported !== "object" || exported === null) {
18489
+ throw new Error("Config must be a JSON object");
18482
18490
  }
18483
- let raw;
18491
+ return exported;
18492
+ }
18493
+ async function loadJsonConfig(resolvedConfigPath) {
18494
+ let fileContents;
18495
+ try {
18496
+ fileContents = await (0, import_promises.readFile)(resolvedConfigPath, "utf-8");
18497
+ } catch {
18498
+ throw new Error(`Config file not found: ${resolvedConfigPath}`);
18499
+ }
18500
+ let parsed2;
18501
+ try {
18502
+ parsed2 = JSON.parse(fileContents);
18503
+ } catch {
18504
+ throw new Error(`Config file is not valid JSON: ${resolvedConfigPath}`);
18505
+ }
18506
+ if (typeof parsed2 !== "object" || parsed2 === null) {
18507
+ throw new Error("Config must be a JSON object");
18508
+ }
18509
+ return parsed2;
18510
+ }
18511
+ async function loadRawConfig(resolvedConfigPath) {
18484
18512
  if (isJsConfigPath(resolvedConfigPath)) {
18485
- let mod;
18486
- try {
18487
- mod = await import((0, import_node_url.pathToFileURL)(resolvedConfigPath).href);
18488
- } catch (err) {
18489
- const message = err instanceof Error ? err.message : String(err);
18490
- throw new Error(`Failed to load JS config file: ${resolvedConfigPath}
18491
- ${message}`);
18492
- }
18493
- const exported = mod["default"] ?? mod;
18494
- if (typeof exported !== "object" || exported === null) {
18495
- throw new Error("Config must be a JSON object");
18496
- }
18497
- raw = exported;
18498
- } else {
18499
- let fileContents;
18500
- try {
18501
- fileContents = await (0, import_promises.readFile)(resolvedConfigPath, "utf-8");
18502
- } catch {
18503
- throw new Error(`Config file not found: ${resolvedConfigPath}`);
18504
- }
18505
- let parsed2;
18506
- try {
18507
- parsed2 = JSON.parse(fileContents);
18508
- } catch {
18509
- throw new Error(`Config file is not valid JSON: ${resolvedConfigPath}`);
18510
- }
18511
- if (typeof parsed2 !== "object" || parsed2 === null) {
18512
- throw new Error("Config must be a JSON object");
18513
- }
18514
- raw = parsed2;
18513
+ return loadJsConfig(resolvedConfigPath);
18515
18514
  }
18515
+ return loadJsonConfig(resolvedConfigPath);
18516
+ }
18517
+ function parseBaseConfig(raw, cwd) {
18516
18518
  if (typeof raw["input_openapi"] !== "string" || !raw["input_openapi"]) {
18517
18519
  throw new Error('Config missing required field: "input_openapi" (path to OpenAPI 3.1 spec)');
18518
18520
  }
@@ -18521,34 +18523,80 @@ ${message}`);
18521
18523
  }
18522
18524
  const input_openapi = raw["input_openapi"];
18523
18525
  const output = raw["output"];
18524
- validateInputPath((0, import_node_path.resolve)(opts.cwd, input_openapi));
18525
- validateOutputPath((0, import_node_path.resolve)(opts.cwd, output));
18526
- const base = { input_openapi, output };
18527
- return opts.parse(raw, base, opts.cwd);
18526
+ validateInputPath((0, import_node_path.resolve)(cwd, input_openapi));
18527
+ validateOutputPath((0, import_node_path.resolve)(cwd, output));
18528
+ return { input_openapi, output };
18528
18529
  }
18529
-
18530
- // src/config.ts
18531
- async function loadConfig(cwd, configPath) {
18532
- return loadConfigFile({
18533
- cwd,
18534
- configPath,
18535
- defaultFileName: "openapi-server.config.json",
18536
- parse: (raw) => {
18537
- const framework = raw["framework"];
18538
- if (framework !== void 0 && framework !== "hono" && framework !== "express" && framework !== "fastify" && framework !== "none") {
18539
- throw new Error('"framework" must be one of: "hono", "express", "fastify", or "none"');
18540
- }
18541
- if (raw["input_schema"] !== void 0 && (typeof raw["input_schema"] !== "string" || !raw["input_schema"])) {
18542
- throw new Error('"input_schema" must be a non-empty string path to your Zod schema file');
18543
- }
18544
- return {
18545
- input_openapi: raw["input_openapi"],
18546
- output: raw["output"],
18547
- framework,
18548
- input_schema: raw["input_schema"]
18549
- };
18530
+ async function prepareRaw(opts) {
18531
+ const resolvedConfigPath = opts.configPath ?? (0, import_node_path.join)(opts.cwd, opts.defaultFileName);
18532
+ if (opts.configPath !== void 0) {
18533
+ validateConfigPath(opts.configPath);
18534
+ }
18535
+ return { raw: await loadRawConfig(resolvedConfigPath) };
18536
+ }
18537
+ function parseProjectEntry(entry, index, opts) {
18538
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
18539
+ throw new Error(`projects[${index}]: entry must be a config object`);
18540
+ }
18541
+ const projectRaw = entry;
18542
+ let base;
18543
+ try {
18544
+ base = parseBaseConfig(projectRaw, opts.cwd);
18545
+ } catch (err) {
18546
+ const message = err instanceof Error ? err.message : String(err);
18547
+ throw new Error(`projects[${index}]: ${message}`);
18548
+ }
18549
+ try {
18550
+ return opts.parse(projectRaw, base, opts.cwd);
18551
+ } catch (err) {
18552
+ const message = err instanceof Error ? err.message : String(err);
18553
+ throw new Error(`projects[${index}]: ${message}`);
18554
+ }
18555
+ }
18556
+ async function loadConfigsFile(opts) {
18557
+ const { raw } = await prepareRaw(opts);
18558
+ if ("projects" in raw) {
18559
+ return parseProjectsArray(raw, opts);
18560
+ }
18561
+ const base = parseBaseConfig(raw, opts.cwd);
18562
+ return [opts.parse(raw, base, opts.cwd)];
18563
+ }
18564
+ function parseProjectsArray(raw, opts) {
18565
+ const hasTopLevelInput = "input_openapi" in raw && raw["input_openapi"] !== void 0;
18566
+ const hasTopLevelOutput = "output" in raw && raw["output"] !== void 0;
18567
+ if (hasTopLevelInput || hasTopLevelOutput) {
18568
+ throw new Error('Config cannot have both top-level "input_openapi"/"output" and a "projects" array. Use one form or the other.');
18569
+ }
18570
+ const projects = raw["projects"];
18571
+ if (!Array.isArray(projects)) {
18572
+ throw new Error('"projects" must be an array of config objects');
18573
+ }
18574
+ if (projects.length === 0) {
18575
+ throw new Error('"projects" array must contain at least one config entry');
18576
+ }
18577
+ return projects.map((entry, index) => parseProjectEntry(entry, index, opts));
18578
+ }
18579
+ async function runProjects(configs, generateOne2) {
18580
+ if (configs.length === 0) {
18581
+ throw new Error("runProjects requires at least one config entry");
18582
+ }
18583
+ if (configs.length === 1) {
18584
+ await generateOne2(configs[0]);
18585
+ return;
18586
+ }
18587
+ for (let i = 0; i < configs.length; i++) {
18588
+ const label = `${i + 1}/${configs.length}`;
18589
+ console.log(`
18590
+ [${label}] generating ${configs[i].input_openapi}...`);
18591
+ try {
18592
+ await generateOne2(configs[i], label);
18593
+ } catch (err) {
18594
+ const message = err instanceof Error ? err.message : String(err);
18595
+ throw new Error(`[${label}] Project failed (${configs[i].input_openapi}): ${message}`);
18550
18596
  }
18551
- });
18597
+ }
18598
+ console.log(`
18599
+ All ${configs.length} projects generated successfully.`);
18552
18600
  }
18553
18601
 
18554
18602
  // ../openapi-zod-ts/dist/parser.js
@@ -18787,7 +18835,31 @@ function toTypeName(name) {
18787
18835
  return safe.length > 0 ? safe : "_";
18788
18836
  }
18789
18837
 
18790
- // src/plugins/service.ts
18838
+ // src/config.ts
18839
+ function parseServerConfig(raw, base) {
18840
+ const framework = raw["framework"];
18841
+ if (framework !== void 0 && framework !== "hono" && framework !== "express" && framework !== "fastify" && framework !== "none") {
18842
+ throw new Error('"framework" must be one of: "hono", "express", "fastify", or "none"');
18843
+ }
18844
+ if (raw["input_schema"] !== void 0 && (typeof raw["input_schema"] !== "string" || !raw["input_schema"])) {
18845
+ throw new Error('"input_schema" must be a non-empty string path to your Zod schema file');
18846
+ }
18847
+ return {
18848
+ ...base,
18849
+ framework,
18850
+ input_schema: raw["input_schema"]
18851
+ };
18852
+ }
18853
+ async function loadConfigs2(cwd, configPath) {
18854
+ return loadConfigsFile({
18855
+ cwd,
18856
+ configPath,
18857
+ defaultFileName: "openapi-server.config.json",
18858
+ parse: parseServerConfig
18859
+ });
18860
+ }
18861
+
18862
+ // src/plugins/shared.ts
18791
18863
  var SUPPORTED_METHODS = ["get", "post", "put", "patch", "delete"];
18792
18864
  function isRef3(obj) {
18793
18865
  return typeof obj === "object" && obj !== null && "$ref" in obj;
@@ -18799,7 +18871,7 @@ function refToName(ref) {
18799
18871
  function extractPathParamsFromPath(path) {
18800
18872
  const matches = path.match(/\{([^}]+)\}/g);
18801
18873
  if (matches === null) return [];
18802
- return matches.map((m) => sanitizeOperationId2(m.slice(1, -1)));
18874
+ return matches.map((m) => m.slice(1, -1));
18803
18875
  }
18804
18876
  function resolveParam(p, spec) {
18805
18877
  if (!isRef3(p)) return p;
@@ -18866,6 +18938,13 @@ function normalizeParamName(name) {
18866
18938
  const camel = parts.map((part, i) => i === 0 ? part : part[0].toUpperCase() + part.slice(1)).join("");
18867
18939
  return /^[^a-zA-Z_$]/.test(camel) ? `_${camel}` : camel;
18868
18940
  }
18941
+ function schemaToTsType(schema) {
18942
+ if (schema === void 0 || isRef3(schema)) return "string";
18943
+ const s = schema;
18944
+ if (s.type === "number" || s.type === "integer") return "number";
18945
+ if (s.type === "boolean") return "boolean";
18946
+ return "string";
18947
+ }
18869
18948
  function getQueryParams(operation, spec) {
18870
18949
  const parameters = operation.parameters;
18871
18950
  if (parameters === void 0) return [];
@@ -18874,39 +18953,131 @@ function getQueryParams(operation, spec) {
18874
18953
  const resolved = resolveParam(p, spec);
18875
18954
  if (resolved === void 0 || resolved.in !== "query") continue;
18876
18955
  const schema = resolved.schema;
18877
- let tsType = "string";
18956
+ const resolvedStyle = resolved.style;
18957
+ const resolvedExplode = resolved.explode;
18958
+ const param = {
18959
+ name: normalizeParamName(resolved.name),
18960
+ rawName: resolved.name,
18961
+ tsType: schemaToTsType(schema),
18962
+ required: resolved.required === true
18963
+ };
18964
+ if (resolvedStyle === "deepObject" && schema !== void 0 && !isRef3(schema)) {
18965
+ const s = schema;
18966
+ if (s.type === "object" && s.properties !== void 0) {
18967
+ param.isDeepObject = true;
18968
+ param.deepObjectProperties = Object.entries(s.properties).map(([key, propSchema]) => ({
18969
+ key,
18970
+ tsType: schemaToTsType(propSchema)
18971
+ }));
18972
+ }
18973
+ }
18974
+ if (!param.isDeepObject && schema !== void 0 && !isRef3(schema) && schema.type === "array" && resolvedExplode === false) {
18975
+ if (resolvedStyle === "spaceDelimited") {
18976
+ param.delimiterStyle = "ssv";
18977
+ } else if (resolvedStyle === "pipeDelimited") {
18978
+ param.delimiterStyle = "psv";
18979
+ } else {
18980
+ param.delimiterStyle = "csv";
18981
+ }
18982
+ }
18878
18983
  if (schema !== void 0 && !isRef3(schema)) {
18879
18984
  const s = schema;
18880
- if (s.type === "number" || s.type === "integer") tsType = "number";
18881
- else if (s.type === "boolean") tsType = "boolean";
18985
+ if (Array.isArray(s.enum)) param.enum = s.enum;
18986
+ if (typeof s.minimum === "number") param.minimum = s.minimum;
18987
+ if (typeof s.maximum === "number") param.maximum = s.maximum;
18988
+ if (typeof s.exclusiveMinimum === "number") param.exclusiveMinimum = s.exclusiveMinimum;
18989
+ if (typeof s.exclusiveMaximum === "number") param.exclusiveMaximum = s.exclusiveMaximum;
18990
+ if (typeof s.minLength === "number") param.minLength = s.minLength;
18991
+ if (typeof s.maxLength === "number") param.maxLength = s.maxLength;
18992
+ if (typeof s.pattern === "string") param.pattern = s.pattern;
18882
18993
  }
18883
- result.push({
18884
- name: normalizeParamName(resolved.name),
18885
- tsType,
18886
- required: resolved.required === true
18887
- });
18994
+ result.push(param);
18888
18995
  }
18889
18996
  return result;
18890
18997
  }
18891
18998
  function getBodyInfo(operation) {
18892
18999
  const requestBody = operation.requestBody;
18893
19000
  if (requestBody === void 0) return void 0;
18894
- if (isRef3(requestBody)) return { typeName: void 0 };
19001
+ if (isRef3(requestBody)) {
19002
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19003
+ }
18895
19004
  const rb = requestBody;
18896
19005
  const content = rb.content;
18897
- if (content === void 0) return { typeName: void 0 };
19006
+ if (content === void 0) {
19007
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19008
+ }
18898
19009
  const jsonContent = content["application/json"];
18899
- if (jsonContent === void 0 || jsonContent.schema === void 0) return { typeName: void 0 };
18900
- const schema = jsonContent.schema;
18901
- if (isRef3(schema)) {
18902
- return { typeName: refToName(schema.$ref) };
19010
+ if (jsonContent !== void 0 && jsonContent.schema !== void 0) {
19011
+ const schema = jsonContent.schema;
19012
+ if (isRef3(schema)) {
19013
+ return {
19014
+ typeName: refToName(schema.$ref),
19015
+ contentType: "application/json",
19016
+ isSynthesized: false
19017
+ };
19018
+ }
19019
+ const operationId = operation.operationId;
19020
+ if (operationId !== void 0 && operationId.length > 0) {
19021
+ return { typeName: toTypeName(operationId), contentType: "application/json", isSynthesized: true };
19022
+ }
19023
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19024
+ }
19025
+ const formContent = content["application/x-www-form-urlencoded"];
19026
+ if (formContent !== void 0) {
19027
+ const schema = formContent.schema;
19028
+ if (schema !== void 0 && isRef3(schema)) {
19029
+ return {
19030
+ typeName: refToName(schema.$ref),
19031
+ contentType: "application/x-www-form-urlencoded",
19032
+ isSynthesized: false
19033
+ };
19034
+ }
19035
+ const operationId = operation.operationId;
19036
+ if (operationId !== void 0 && operationId.length > 0) {
19037
+ return {
19038
+ typeName: toTypeName(operationId),
19039
+ contentType: "application/x-www-form-urlencoded",
19040
+ isSynthesized: true
19041
+ };
19042
+ }
19043
+ return { typeName: void 0, contentType: "application/x-www-form-urlencoded", isSynthesized: false };
18903
19044
  }
18904
- return { typeName: void 0 };
19045
+ const multipartContent = content["multipart/form-data"];
19046
+ if (multipartContent !== void 0) {
19047
+ const schema = multipartContent.schema;
19048
+ if (schema !== void 0 && isRef3(schema)) {
19049
+ return {
19050
+ typeName: refToName(schema.$ref),
19051
+ contentType: "multipart/form-data",
19052
+ isSynthesized: false
19053
+ };
19054
+ }
19055
+ const operationId = operation.operationId;
19056
+ if (operationId !== void 0 && operationId.length > 0) {
19057
+ return {
19058
+ typeName: toTypeName(operationId),
19059
+ contentType: "multipart/form-data",
19060
+ isSynthesized: true
19061
+ };
19062
+ }
19063
+ return { typeName: void 0, contentType: "multipart/form-data", isSynthesized: false };
19064
+ }
19065
+ return { typeName: void 0, contentType: "application/json", isSynthesized: false };
19066
+ }
19067
+
19068
+ // src/plugins/service.ts
19069
+ function collectContentfulTwoxxCodes(responses) {
19070
+ return Object.keys(responses).filter((k) => /^2\d\d$/.test(k) && k !== "204").sort();
18905
19071
  }
18906
19072
  function getReturnInfo(operation) {
18907
19073
  const responses = operation.responses;
18908
19074
  if (responses === void 0) return { typeName: void 0, isArray: false, isVoid: true };
18909
- for (const code of ["200", "201"]) {
19075
+ const contentfulCodes = collectContentfulTwoxxCodes(responses);
19076
+ const isMultiStatus = contentfulCodes.length > 1;
19077
+ const twoxxCodes = ["200", "201", ...Object.keys(responses).filter(
19078
+ (k) => /^2\d\d$/.test(k) && k !== "200" && k !== "201" && k !== "204"
19079
+ )];
19080
+ for (const code of twoxxCodes) {
18910
19081
  const response = responses[code];
18911
19082
  if (response === void 0) continue;
18912
19083
  if (isRef3(response)) continue;
@@ -18914,28 +19085,37 @@ function getReturnInfo(operation) {
18914
19085
  const content = resp.content;
18915
19086
  if (content === void 0) continue;
18916
19087
  const jsonContent = content["application/json"];
18917
- if (jsonContent === void 0 || jsonContent.schema === void 0) continue;
18918
- const schema = jsonContent.schema;
18919
- if (isRef3(schema)) {
18920
- return {
18921
- typeName: refToName(schema.$ref),
18922
- isArray: false,
18923
- isVoid: false
18924
- };
18925
- }
18926
- const s = schema;
18927
- if (s.type === "array") {
18928
- const items = s.items;
18929
- if (items !== void 0 && isRef3(items)) {
19088
+ if (jsonContent !== void 0 && jsonContent.schema !== void 0) {
19089
+ const schema = jsonContent.schema;
19090
+ if (isRef3(schema)) {
18930
19091
  return {
18931
- typeName: refToName(items.$ref),
18932
- isArray: true,
18933
- isVoid: false
19092
+ typeName: refToName(schema.$ref),
19093
+ isArray: false,
19094
+ isVoid: false,
19095
+ isMultiStatus
18934
19096
  };
18935
19097
  }
18936
- return { typeName: void 0, isArray: true, isVoid: false };
19098
+ const s = schema;
19099
+ if (s.type === "array") {
19100
+ const items = s.items;
19101
+ if (items !== void 0 && isRef3(items)) {
19102
+ return {
19103
+ typeName: refToName(items.$ref),
19104
+ isArray: true,
19105
+ isVoid: false,
19106
+ isMultiStatus
19107
+ };
19108
+ }
19109
+ return { typeName: void 0, isArray: true, isVoid: false, isMultiStatus };
19110
+ }
19111
+ return { typeName: void 0, isArray: false, isVoid: false, isMultiStatus };
19112
+ }
19113
+ if (content["text/plain"] !== void 0) {
19114
+ return { typeName: void 0, isArray: false, isVoid: false, primitiveType: "string" };
19115
+ }
19116
+ if (content["application/octet-stream"] !== void 0) {
19117
+ return { typeName: void 0, isArray: false, isVoid: false, primitiveType: "Uint8Array" };
18937
19118
  }
18938
- return { typeName: void 0, isArray: false, isVoid: false };
18939
19119
  }
18940
19120
  if (responses["204"] !== void 0) {
18941
19121
  return { typeName: void 0, isArray: false, isVoid: true };
@@ -18944,6 +19124,16 @@ function getReturnInfo(operation) {
18944
19124
  }
18945
19125
  function buildReturnType(info) {
18946
19126
  if (info.isVoid) return "Promise<void>";
19127
+ if (info.primitiveType !== void 0) return `Promise<${info.primitiveType}>`;
19128
+ if (info.isMultiStatus === true) {
19129
+ let bodyType;
19130
+ if (info.typeName !== void 0) {
19131
+ bodyType = info.isArray ? `${info.typeName}[]` : info.typeName;
19132
+ } else {
19133
+ bodyType = info.isArray ? "unknown[]" : "unknown";
19134
+ }
19135
+ return `Promise<{ status: number; body: ${bodyType} }>`;
19136
+ }
18947
19137
  if (info.typeName !== void 0) {
18948
19138
  return info.isArray ? `Promise<${info.typeName}[]>` : `Promise<${info.typeName}>`;
18949
19139
  }
@@ -18978,10 +19168,10 @@ function collectOperations(spec) {
18978
19168
  function buildMethodSignature(op) {
18979
19169
  const args = [];
18980
19170
  for (const p of op.pathParams) {
18981
- args.push(`${p}: string`);
19171
+ args.push(`${sanitizeOperationId2(p)}: string`);
18982
19172
  }
18983
19173
  if (op.bodyInfo !== void 0) {
18984
- const typeName = op.bodyInfo.typeName ?? "unknown";
19174
+ const typeName = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
18985
19175
  args.push(`body: ${typeName}`);
18986
19176
  }
18987
19177
  if (op.queryParams.length > 0) {
@@ -18999,7 +19189,7 @@ function generateService(spec) {
18999
19189
  const operations = collectOperations(spec);
19000
19190
  const importTypes = /* @__PURE__ */ new Set();
19001
19191
  for (const op of operations) {
19002
- if (op.bodyInfo?.typeName !== void 0) {
19192
+ if (op.bodyInfo?.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19003
19193
  importTypes.add(op.bodyInfo.typeName);
19004
19194
  }
19005
19195
  if (op.returnInfo.typeName !== void 0) {
@@ -19028,110 +19218,9 @@ function generateService(spec) {
19028
19218
  }
19029
19219
 
19030
19220
  // src/plugins/router.ts
19031
- var SUPPORTED_METHODS2 = ["get", "post", "put", "patch", "delete"];
19032
- function isRef4(obj) {
19033
- return typeof obj === "object" && obj !== null && "$ref" in obj;
19034
- }
19035
- function refToName2(ref) {
19036
- const parts = ref.split("/");
19037
- return toTypeName(parts[parts.length - 1]);
19038
- }
19039
- function extractPathParamsFromPath2(path) {
19040
- const matches = path.match(/\{([^}]+)\}/g);
19041
- if (matches === null) return [];
19042
- return matches.map((m) => m.slice(1, -1));
19043
- }
19044
19221
  function toHonoPath(openapiPath) {
19045
19222
  return openapiPath.replace(/\{([^}]+)\}/g, ":$1");
19046
19223
  }
19047
- function resolveParam2(p, spec) {
19048
- if (!isRef4(p)) return p;
19049
- const refStr = p.$ref;
19050
- const name = refToName2(refStr);
19051
- const components = spec.components;
19052
- if (components?.parameters === void 0) return void 0;
19053
- const resolved = components.parameters[name];
19054
- if (resolved === void 0 || isRef4(resolved)) return void 0;
19055
- return resolved;
19056
- }
19057
- function deriveServiceName2(spec) {
19058
- const title = spec.info?.title ?? "";
19059
- const pascal = title.replace(/[^a-zA-Z0-9 ]/g, "").split(/\s+/).filter((s) => s.length > 0).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
19060
- if (pascal.length === 0) return "ApiService";
19061
- const safePascal = /^[0-9]/.test(pascal) ? `_${pascal}` : pascal;
19062
- if (safePascal.endsWith("Service")) return safePascal;
19063
- return `${safePascal}Service`;
19064
- }
19065
- function sanitizeOperationId3(id) {
19066
- const parts = id.replace(/'/g, "").split(/[^a-zA-Z0-9]+/).filter(Boolean);
19067
- if (parts.length === 0) return "unknown";
19068
- const [first = "", ...rest] = parts;
19069
- const camel = first.charAt(0).toLowerCase() + first.slice(1) + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
19070
- return /^[0-9]/.test(camel) ? `_${camel}` : camel;
19071
- }
19072
- function deriveMethodName2(operationId, method, path) {
19073
- if (operationId !== void 0 && operationId.length > 0) {
19074
- return sanitizeOperationId3(operationId);
19075
- }
19076
- return deriveOperationName3(method, path);
19077
- }
19078
- function deriveOperationName3(method, path) {
19079
- const prefixMap = {
19080
- get: "get",
19081
- post: "create",
19082
- put: "update",
19083
- patch: "patch",
19084
- delete: "delete"
19085
- };
19086
- const prefix = prefixMap[method] ?? method;
19087
- const segments = path.replace(/^\/api\/v\d+\//, "").replace(/^\//, "");
19088
- const parts = segments.split("/").map((seg) => {
19089
- const paramMatches = seg.match(/\{([^}]+)\}/g);
19090
- if (paramMatches !== null && !(seg.startsWith("{") && seg.endsWith("}"))) {
19091
- return paramMatches.map((m) => {
19092
- const name = sanitizeOperationId3(m.slice(1, -1));
19093
- return "By" + name.charAt(0).toUpperCase() + name.slice(1);
19094
- }).join("");
19095
- }
19096
- if (seg.startsWith("{") && seg.endsWith("}")) {
19097
- const name = seg.slice(1, -1);
19098
- const sanitized = sanitizeOperationId3(name);
19099
- return "By" + sanitized.charAt(0).toUpperCase() + sanitized.slice(1);
19100
- }
19101
- return toTypeName(seg);
19102
- });
19103
- return prefix + parts.join("");
19104
- }
19105
- function normalizeParamName2(name) {
19106
- const stripped = name.replace(/\[\]$/, "").replace(/'/g, "");
19107
- const parts = stripped.split(/[^a-zA-Z0-9]+/).filter(Boolean);
19108
- if (parts.length === 0) return "_";
19109
- const camel = parts.map((part, i) => i === 0 ? part : part[0].toUpperCase() + part.slice(1)).join("");
19110
- return /^[^a-zA-Z_$]/.test(camel) ? `_${camel}` : camel;
19111
- }
19112
- function schemaToTsType(schema) {
19113
- if (schema === void 0 || isRef4(schema)) return "string";
19114
- const s = schema;
19115
- if (s.type === "number" || s.type === "integer") return "number";
19116
- if (s.type === "boolean") return "boolean";
19117
- return "string";
19118
- }
19119
- function getQueryParams2(operation, spec) {
19120
- const parameters = operation.parameters;
19121
- if (parameters === void 0) return [];
19122
- const result = [];
19123
- for (const p of parameters) {
19124
- const resolved = resolveParam2(p, spec);
19125
- if (resolved === void 0 || resolved.in !== "query") continue;
19126
- const schema = resolved.schema;
19127
- result.push({
19128
- name: normalizeParamName2(resolved.name),
19129
- tsType: schemaToTsType(schema),
19130
- required: resolved.required === true
19131
- });
19132
- }
19133
- return result;
19134
- }
19135
19224
  function formatToZodModifier(format) {
19136
19225
  switch (format) {
19137
19226
  case "uuid":
@@ -19148,8 +19237,23 @@ function formatToZodModifier(format) {
19148
19237
  }
19149
19238
  }
19150
19239
  function pathParamZodExpr(schema) {
19151
- if (schema === void 0 || isRef4(schema)) return void 0;
19240
+ if (schema === void 0 || isRef3(schema)) return void 0;
19152
19241
  const s = schema;
19242
+ if (s.type === "integer" || s.type === "number") {
19243
+ const hasMin = typeof s.minimum === "number";
19244
+ const hasMax = typeof s.maximum === "number";
19245
+ const hasExcMin = typeof s.exclusiveMinimum === "number";
19246
+ const hasExcMax = typeof s.exclusiveMaximum === "number";
19247
+ if (hasMin || hasMax || hasExcMin || hasExcMax) {
19248
+ let expr = "z.coerce.number()";
19249
+ if (hasMin) expr += `.min(${s.minimum})`;
19250
+ if (hasMax) expr += `.max(${s.maximum})`;
19251
+ if (hasExcMin) expr += `.gt(${s.exclusiveMinimum})`;
19252
+ if (hasExcMax) expr += `.lt(${s.exclusiveMaximum})`;
19253
+ return expr;
19254
+ }
19255
+ return void 0;
19256
+ }
19153
19257
  if (s.type !== "string") return void 0;
19154
19258
  const format = s.format;
19155
19259
  if (format === void 0) return void 0;
@@ -19157,30 +19261,71 @@ function pathParamZodExpr(schema) {
19157
19261
  if (modifier === "") return void 0;
19158
19262
  return `z.string()${modifier}`;
19159
19263
  }
19160
- function paramZodExpr(tsType, required, schema) {
19264
+ function queryParamDelimitedZodBase(_param) {
19265
+ return "z.array(z.string())";
19266
+ }
19267
+ function queryParamDeepObjectZodBase(param) {
19268
+ const propFields = (param.deepObjectProperties ?? []).map((p) => {
19269
+ const coerced = p.tsType === "number" ? "z.coerce.number()" : "z.string()";
19270
+ return `${p.key}: ${coerced}.optional()`;
19271
+ });
19272
+ return `z.object({ ${propFields.join(", ")} })`;
19273
+ }
19274
+ function queryParamNumberZodBase(param) {
19275
+ let base = "z.number()";
19276
+ if (param.minimum !== void 0) base += `.min(${param.minimum})`;
19277
+ if (param.maximum !== void 0) base += `.max(${param.maximum})`;
19278
+ if (param.exclusiveMinimum !== void 0) base += `.gt(${param.exclusiveMinimum})`;
19279
+ if (param.exclusiveMaximum !== void 0) base += `.lt(${param.exclusiveMaximum})`;
19280
+ return base;
19281
+ }
19282
+ function queryParamStringZodBase(param) {
19283
+ let base;
19284
+ if (param.enum !== void 0 && param.enum.length > 0) {
19285
+ const members = param.enum.map((v) => JSON.stringify(v)).join(", ");
19286
+ base = `z.enum([${members}])`;
19287
+ } else {
19288
+ base = "z.string()";
19289
+ }
19290
+ if (param.minLength !== void 0) base += `.min(${param.minLength})`;
19291
+ if (param.maxLength !== void 0) base += `.max(${param.maxLength})`;
19292
+ if (param.pattern !== void 0) base += `.regex(/${param.pattern}/)`;
19293
+ return base;
19294
+ }
19295
+ function queryParamZodExpr(param) {
19161
19296
  let base;
19162
- if (tsType === "number") {
19163
- base = "z.number()";
19164
- } else if (tsType === "boolean") {
19297
+ if (param.delimiterStyle !== void 0) {
19298
+ base = queryParamDelimitedZodBase(param);
19299
+ } else if (param.isDeepObject === true && param.deepObjectProperties !== void 0) {
19300
+ base = queryParamDeepObjectZodBase(param);
19301
+ } else if (param.tsType === "number") {
19302
+ base = queryParamNumberZodBase(param);
19303
+ } else if (param.tsType === "boolean") {
19165
19304
  base = "z.boolean()";
19166
19305
  } else {
19167
- if (schema !== void 0 && !isRef4(schema)) {
19168
- const s = schema;
19169
- const format = s.format;
19170
- const modifier = format !== void 0 ? formatToZodModifier(format) : "";
19171
- base = `z.string()${modifier}`;
19172
- } else {
19173
- base = "z.string()";
19174
- }
19306
+ base = queryParamStringZodBase(param);
19175
19307
  }
19176
- return required ? base : `${base}.optional()`;
19308
+ return param.required ? base : `${base}.optional()`;
19309
+ }
19310
+ function headerParamZodExpr(param) {
19311
+ let base;
19312
+ if (param.enum !== void 0 && param.enum.length > 0) {
19313
+ const members = param.enum.map((v) => JSON.stringify(v)).join(", ");
19314
+ base = `z.enum([${members}])`;
19315
+ } else {
19316
+ base = "z.string()";
19317
+ }
19318
+ if (param.minLength !== void 0) base += `.min(${param.minLength})`;
19319
+ if (param.maxLength !== void 0) base += `.max(${param.maxLength})`;
19320
+ if (param.pattern !== void 0) base += `.regex(/${param.pattern}/)`;
19321
+ return param.required ? base : `${base}.optional()`;
19177
19322
  }
19178
19323
  function getPathParamValidations(operation, spec, rawPathParamNames) {
19179
19324
  const parameters = operation.parameters;
19180
19325
  if (parameters === void 0) return [];
19181
19326
  const zodByName = /* @__PURE__ */ new Map();
19182
19327
  for (const p of parameters) {
19183
- const resolved = resolveParam2(p, spec);
19328
+ const resolved = resolveParam(p, spec);
19184
19329
  if (resolved === void 0 || resolved.in !== "path") continue;
19185
19330
  const schema = resolved.schema;
19186
19331
  const zodExpr = pathParamZodExpr(schema);
@@ -19198,23 +19343,53 @@ function getHeaderParams(operation, spec) {
19198
19343
  if (parameters === void 0) return [];
19199
19344
  const result = [];
19200
19345
  for (const p of parameters) {
19201
- const resolved = resolveParam2(p, spec);
19346
+ const resolved = resolveParam(p, spec);
19202
19347
  if (resolved === void 0 || resolved.in !== "header") continue;
19203
- result.push({
19348
+ const param = {
19204
19349
  rawName: resolved.name,
19205
19350
  required: resolved.required === true
19206
- });
19351
+ };
19352
+ const schema = resolved.schema;
19353
+ if (schema !== void 0 && !isRef3(schema)) {
19354
+ const s = schema;
19355
+ if (Array.isArray(s.enum)) param.enum = s.enum;
19356
+ if (typeof s.minLength === "number") param.minLength = s.minLength;
19357
+ if (typeof s.maxLength === "number") param.maxLength = s.maxLength;
19358
+ if (typeof s.pattern === "string") param.pattern = s.pattern;
19359
+ }
19360
+ result.push(param);
19207
19361
  }
19208
19362
  return result;
19209
19363
  }
19364
+ function queryParamHasConstraints(q) {
19365
+ const constraintFields = [
19366
+ q.enum,
19367
+ q.minimum,
19368
+ q.maximum,
19369
+ q.exclusiveMinimum,
19370
+ q.exclusiveMaximum,
19371
+ q.minLength,
19372
+ q.maxLength,
19373
+ q.pattern,
19374
+ q.delimiterStyle
19375
+ ];
19376
+ return constraintFields.some((f) => f !== void 0) || q.isDeepObject === true;
19377
+ }
19210
19378
  function queryParamsNeedValidation(queryParams) {
19211
- return queryParams.some((q) => q.required || q.tsType !== "string");
19379
+ return queryParams.some(
19380
+ (q) => q.required || q.tsType !== "string" || queryParamHasConstraints(q)
19381
+ );
19382
+ }
19383
+ function delimiterChar(style) {
19384
+ if (style === "ssv") return " ";
19385
+ if (style === "psv") return "|";
19386
+ return ",";
19212
19387
  }
19213
19388
  function emitQueryValidation(lines, queryParams, indent) {
19214
19389
  const inner = `${indent} `;
19215
19390
  const fieldIndent = `${indent} `;
19216
19391
  const fields = queryParams.map((q) => {
19217
- const expr = paramZodExpr(q.tsType, q.required);
19392
+ const expr = queryParamZodExpr(q);
19218
19393
  return `${fieldIndent}${q.name}: ${expr}`;
19219
19394
  }).join(",\n");
19220
19395
  lines.push(`${inner}// Validate query parameters: returns 422 with Zod issues on failure`);
@@ -19253,7 +19428,7 @@ function emitHeaderValidation(lines, headerParams, indent, framework) {
19253
19428
  const fieldIndent = `${indent} `;
19254
19429
  const schemaFields = headerParams.map((h) => {
19255
19430
  const key = JSON.stringify(h.rawName);
19256
- const expr = h.required ? "z.string()" : "z.string().optional()";
19431
+ const expr = headerParamZodExpr(h);
19257
19432
  return `${fieldIndent}${key}: ${expr}`;
19258
19433
  }).join(",\n");
19259
19434
  const rawFields = headerParams.map((h) => {
@@ -19275,54 +19450,84 @@ function emitHeaderValidation(lines, headerParams, indent, framework) {
19275
19450
  lines.push(rawFields);
19276
19451
  lines.push(`${inner}})`);
19277
19452
  }
19278
- function getBodyInfo2(operation) {
19279
- const requestBody = operation.requestBody;
19280
- if (requestBody === void 0) return void 0;
19281
- if (isRef4(requestBody)) return { typeName: void 0 };
19282
- const rb = requestBody;
19283
- const content = rb.content;
19284
- if (content === void 0) return { typeName: void 0 };
19285
- const jsonContent = content["application/json"];
19286
- if (jsonContent === void 0 || jsonContent.schema === void 0) return { typeName: void 0 };
19287
- const schema = jsonContent.schema;
19288
- if (isRef4(schema)) {
19289
- return { typeName: refToName2(schema.$ref) };
19290
- }
19291
- return { typeName: void 0 };
19292
- }
19293
19453
  function response200IsVoid(resp) {
19294
- if (isRef4(resp)) return false;
19454
+ if (isRef3(resp)) return false;
19295
19455
  const r = resp;
19296
19456
  const content = r.content;
19297
19457
  return content === void 0 || Object.keys(content).length === 0;
19298
19458
  }
19459
+ function detectResponseContentType(resp) {
19460
+ if (isRef3(resp)) return "application/json";
19461
+ const r = resp;
19462
+ const content = r.content;
19463
+ if (content === void 0) return "application/json";
19464
+ if ("text/plain" in content) return "text/plain";
19465
+ if ("application/octet-stream" in content) return "application/octet-stream";
19466
+ return "application/json";
19467
+ }
19299
19468
  function getResponseStatus(operation, httpMethod) {
19300
19469
  const responses = operation.responses;
19301
19470
  if (responses === void 0) {
19302
- return httpMethod === "delete" ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
19471
+ return httpMethod === "delete" ? { status: 204, isVoid: true, responseContentType: "application/json" } : { status: 200, isVoid: false, responseContentType: "application/json" };
19472
+ }
19473
+ const contentfulTwoxxKeys = Object.keys(responses).filter((k) => /^2\d\d$/.test(k) && k !== "204").sort();
19474
+ if (contentfulTwoxxKeys.length > 1) {
19475
+ return {
19476
+ status: 200,
19477
+ isVoid: false,
19478
+ responseContentType: "application/json",
19479
+ isMultiStatus: true
19480
+ };
19481
+ }
19482
+ if (responses["201"] !== void 0) {
19483
+ return {
19484
+ status: 201,
19485
+ isVoid: false,
19486
+ responseContentType: detectResponseContentType(responses["201"])
19487
+ };
19488
+ }
19489
+ if (responses["204"] !== void 0) {
19490
+ return { status: 204, isVoid: true, responseContentType: "application/json" };
19303
19491
  }
19304
- if (responses["201"] !== void 0) return { status: 201, isVoid: false };
19305
- if (responses["204"] !== void 0) return { status: 204, isVoid: true };
19306
19492
  if (responses["200"] !== void 0) {
19307
- if (response200IsVoid(responses["200"])) return { status: 204, isVoid: true };
19308
- return { status: 200, isVoid: false };
19493
+ if (response200IsVoid(responses["200"])) {
19494
+ return { status: 204, isVoid: true, responseContentType: "application/json" };
19495
+ }
19496
+ return {
19497
+ status: 200,
19498
+ isVoid: false,
19499
+ responseContentType: detectResponseContentType(responses["200"])
19500
+ };
19309
19501
  }
19310
- return httpMethod === "delete" ? { status: 204, isVoid: true } : { status: 200, isVoid: false };
19502
+ const twoxxKeys = Object.keys(responses).filter(
19503
+ (k) => /^2\d\d$/.test(k) && k !== "200" && k !== "201" && k !== "204"
19504
+ );
19505
+ if (twoxxKeys.length === 1) {
19506
+ const code = parseInt(twoxxKeys[0], 10);
19507
+ const resp = responses[twoxxKeys[0]];
19508
+ const isVoid = isRef3(resp) ? false : (() => {
19509
+ const r = resp;
19510
+ const content = r.content;
19511
+ return content === void 0 || Object.keys(content).length === 0;
19512
+ })();
19513
+ return { status: code, isVoid, responseContentType: detectResponseContentType(resp) };
19514
+ }
19515
+ return httpMethod === "delete" ? { status: 204, isVoid: true, responseContentType: "application/json" } : { status: 200, isVoid: false, responseContentType: "application/json" };
19311
19516
  }
19312
19517
  function collectOperations2(spec) {
19313
19518
  const paths = spec.paths;
19314
19519
  if (paths === void 0) return [];
19315
19520
  const operations = [];
19316
19521
  for (const [path, pathItem] of Object.entries(paths)) {
19317
- for (const method of SUPPORTED_METHODS2) {
19522
+ for (const method of SUPPORTED_METHODS) {
19318
19523
  const operation = pathItem[method];
19319
19524
  if (operation === void 0) continue;
19320
- const methodName = deriveMethodName2(operation.operationId, method, path);
19321
- const pathParams = extractPathParamsFromPath2(path);
19525
+ const methodName = deriveMethodName(operation.operationId, method, path);
19526
+ const pathParams = extractPathParamsFromPath(path);
19322
19527
  const pathParamValidations = getPathParamValidations(operation, spec, pathParams);
19323
- const queryParams = getQueryParams2(operation, spec);
19528
+ const queryParams = getQueryParams(operation, spec);
19324
19529
  const headerParams = getHeaderParams(operation, spec);
19325
- const bodyInfo = getBodyInfo2(operation);
19530
+ const bodyInfo = getBodyInfo(operation);
19326
19531
  const responseStatus = getResponseStatus(operation, method);
19327
19532
  operations.push({
19328
19533
  methodName,
@@ -19343,7 +19548,9 @@ function collectOperations2(spec) {
19343
19548
  function collectSortedBodyTypes(operations) {
19344
19549
  const bodyTypes = /* @__PURE__ */ new Set();
19345
19550
  for (const op of operations) {
19346
- if (op.bodyInfo?.typeName !== void 0) bodyTypes.add(op.bodyInfo.typeName);
19551
+ if (op.bodyInfo?.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19552
+ bodyTypes.add(op.bodyInfo.typeName);
19553
+ }
19347
19554
  }
19348
19555
  return Array.from(bodyTypes).sort();
19349
19556
  }
@@ -19375,7 +19582,27 @@ function buildRouteHandler(op, indent, schemaNames) {
19375
19582
  lines.push(`${indent} }`);
19376
19583
  }
19377
19584
  if (op.queryParams.length > 0) {
19585
+ const deepObjectParams = op.queryParams.filter((q) => q.isDeepObject === true);
19586
+ if (deepObjectParams.length > 0) {
19587
+ lines.push(`${indent} const _dq = c.req.queries()`);
19588
+ for (const q of deepObjectParams) {
19589
+ const prefixLen = q.rawName.length + 1;
19590
+ const bracketPrefix = q.rawName + "[";
19591
+ lines.push(`${indent} const ${q.name} = Object.fromEntries(`);
19592
+ lines.push(
19593
+ `${indent} Object.entries(_dq).filter(([k]) => k.startsWith('${bracketPrefix}') && k.endsWith(']')).map(([k, vs]) => [k.slice(${prefixLen}, -1), vs[0]])`
19594
+ );
19595
+ lines.push(`${indent} )`);
19596
+ }
19597
+ }
19378
19598
  const fields = op.queryParams.map((q) => {
19599
+ if (q.isDeepObject === true) {
19600
+ return ` ${q.name}`;
19601
+ }
19602
+ if (q.delimiterStyle !== void 0) {
19603
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19604
+ return ` ${q.name}: c.req.query('${q.rawName}') !== undefined ? c.req.query('${q.rawName}')!.split(${delim}) : undefined`;
19605
+ }
19379
19606
  if (q.tsType === "number") {
19380
19607
  return ` ${q.name}: c.req.query('${q.name}') !== undefined ? Number(c.req.query('${q.name}')) : undefined`;
19381
19608
  }
@@ -19403,8 +19630,32 @@ function buildRouteHandler(op, indent, schemaNames) {
19403
19630
  }
19404
19631
  let bodyVarName = "body";
19405
19632
  if (op.bodyInfo !== void 0) {
19406
- const typeAnnotation = op.bodyInfo.typeName !== void 0 ? `<${op.bodyInfo.typeName}>` : "";
19407
- lines.push(`${indent} const body = await c.req.json${typeAnnotation}()`);
19633
+ const typeDecl = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? op.bodyInfo.typeName : "unknown";
19634
+ if (op.bodyInfo.contentType === "application/x-www-form-urlencoded") {
19635
+ lines.push(`${indent} const _ct = c.req.header('content-type') ?? ''`);
19636
+ lines.push(
19637
+ `${indent} if (!_ct.toLowerCase().startsWith('application/x-www-form-urlencoded')) {`
19638
+ );
19639
+ lines.push(`${indent} return c.json({ error: 'Unsupported Media Type' }, 415)`);
19640
+ lines.push(`${indent} }`);
19641
+ lines.push(`${indent} const body: unknown = await c.req.parseBody()`);
19642
+ } else if (op.bodyInfo.contentType === "multipart/form-data") {
19643
+ lines.push(
19644
+ `${indent} // multipart/form-data: parseBody({ all: true }) collects repeated keys into arrays.`
19645
+ );
19646
+ lines.push(`${indent} const body: unknown = await c.req.parseBody({ all: true })`);
19647
+ } else {
19648
+ lines.push(`${indent} const _ct = c.req.header('content-type') ?? ''`);
19649
+ lines.push(`${indent} if (!_ct.toLowerCase().startsWith('application/json')) {`);
19650
+ lines.push(`${indent} return c.json({ error: 'Unsupported Media Type' }, 415)`);
19651
+ lines.push(`${indent} }`);
19652
+ lines.push(`${indent} let body: ${typeDecl}`);
19653
+ lines.push(`${indent} try {`);
19654
+ lines.push(`${indent} body = JSON.parse(await c.req.text()) as ${typeDecl}`);
19655
+ lines.push(`${indent} } catch {`);
19656
+ lines.push(`${indent} return c.json({ error: 'Invalid JSON body' }, 400)`);
19657
+ lines.push(`${indent} }`);
19658
+ }
19408
19659
  const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19409
19660
  if (schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName)) {
19410
19661
  lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
@@ -19429,14 +19680,44 @@ function buildRouteHandler(op, indent, schemaNames) {
19429
19680
  serviceArgs.push("params");
19430
19681
  }
19431
19682
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19683
+ lines.push(`${indent} try {`);
19432
19684
  if (op.responseStatus.isVoid) {
19433
- lines.push(`${indent} await ${serviceCall}`);
19434
- lines.push(`${indent} return new Response(null, { status: ${op.responseStatus.status} })`);
19435
- } else if (op.responseStatus.status === 201) {
19436
- lines.push(`${indent} return c.json(await ${serviceCall}, 201)`);
19685
+ lines.push(`${indent} await ${serviceCall}`);
19686
+ lines.push(`${indent} return new Response(null, { status: ${op.responseStatus.status} })`);
19687
+ } else if (op.responseStatus.isMultiStatus === true) {
19688
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19689
+ lines.push(`${indent} return c.json(_envelope.body, _envelope.status as any)`);
19690
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19691
+ if (op.responseStatus.status === 200) {
19692
+ lines.push(`${indent} return c.text(await ${serviceCall})`);
19693
+ } else {
19694
+ lines.push(`${indent} return c.text(await ${serviceCall}, ${op.responseStatus.status})`);
19695
+ }
19696
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19697
+ if (op.responseStatus.status === 200) {
19698
+ lines.push(`${indent} const _result = await ${serviceCall}`);
19699
+ lines.push(
19700
+ `${indent} return new Response(_result, { headers: { 'content-type': 'application/octet-stream' } })`
19701
+ );
19702
+ } else {
19703
+ lines.push(`${indent} const _result = await ${serviceCall}`);
19704
+ lines.push(
19705
+ `${indent} return new Response(_result, { status: ${op.responseStatus.status}, headers: { 'content-type': 'application/octet-stream' } })`
19706
+ );
19707
+ }
19708
+ } else if (op.responseStatus.status === 200) {
19709
+ lines.push(`${indent} return c.json(await ${serviceCall})`);
19437
19710
  } else {
19438
- lines.push(`${indent} return c.json(await ${serviceCall})`);
19711
+ lines.push(`${indent} return c.json(await ${serviceCall}, ${op.responseStatus.status})`);
19439
19712
  }
19713
+ lines.push(`${indent} } catch (err) {`);
19714
+ lines.push(`${indent} if (err instanceof HttpError) {`);
19715
+ lines.push(
19716
+ `${indent} return new Response(JSON.stringify({ error: err.message }), { status: err.status, headers: { 'content-type': 'application/json' } })`
19717
+ );
19718
+ lines.push(`${indent} }`);
19719
+ lines.push(`${indent} throw err`);
19720
+ lines.push(`${indent} }`);
19440
19721
  lines.push(`${indent}})`);
19441
19722
  return lines.join("\n");
19442
19723
  }
@@ -19455,6 +19736,13 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19455
19736
  }
19456
19737
  if (op.queryParams.length > 0) {
19457
19738
  const fields = op.queryParams.map((q) => {
19739
+ if (q.isDeepObject === true) {
19740
+ return ` ${q.name}: (req.query['${q.rawName}'] ?? {}) as Record<string, string | undefined>`;
19741
+ }
19742
+ if (q.delimiterStyle !== void 0) {
19743
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19744
+ return ` ${q.name}: typeof req.query['${q.rawName}'] === 'string' ? (req.query['${q.rawName}'] as string).split(${delim}) : undefined`;
19745
+ }
19458
19746
  if (q.tsType === "number") {
19459
19747
  return ` ${q.name}: Number(req.query['${q.name}'] as string)`;
19460
19748
  }
@@ -19485,21 +19773,28 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19485
19773
  }
19486
19774
  let bodyVarName = "body";
19487
19775
  if (op.bodyInfo !== void 0) {
19488
- const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19489
- const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19490
- if (useZod) {
19491
- lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19492
- lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19493
- lines.push(`${indent} if (!parseResult.success) {`);
19776
+ if (op.bodyInfo.contentType === "multipart/form-data") {
19494
19777
  lines.push(
19495
- `${indent} return void res.status(422).json({ error: 'Invalid request body', issues: parseResult.error.issues })`
19778
+ `${indent} // multipart/form-data: assumes multer middleware has populated req.files + req.body.`
19496
19779
  );
19497
- lines.push(`${indent} }`);
19498
- lines.push(`${indent} const validatedBody = parseResult.data`);
19499
- bodyVarName = "validatedBody";
19780
+ lines.push(`${indent} const body = { ...req.body, ...(req as any).files } as unknown`);
19500
19781
  } else {
19501
- const typeAnnotation = op.bodyInfo.typeName !== void 0 ? ` as ${op.bodyInfo.typeName}` : "";
19502
- lines.push(`${indent} const body = req.body${typeAnnotation}`);
19782
+ const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19783
+ const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19784
+ if (useZod) {
19785
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19786
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19787
+ lines.push(`${indent} if (!parseResult.success) {`);
19788
+ lines.push(
19789
+ `${indent} return void res.status(422).json({ error: 'Invalid request body', issues: parseResult.error.issues })`
19790
+ );
19791
+ lines.push(`${indent} }`);
19792
+ lines.push(`${indent} const validatedBody = parseResult.data`);
19793
+ bodyVarName = "validatedBody";
19794
+ } else {
19795
+ const typeAnnotation = op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized ? ` as ${op.bodyInfo.typeName}` : "";
19796
+ lines.push(`${indent} const body = req.body${typeAnnotation}`);
19797
+ }
19503
19798
  }
19504
19799
  }
19505
19800
  const serviceArgs = [];
@@ -19513,14 +19808,42 @@ function buildExpressRouteHandler(op, indent, schemaNames) {
19513
19808
  serviceArgs.push("params");
19514
19809
  }
19515
19810
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19811
+ lines.push(`${indent} try {`);
19516
19812
  if (op.responseStatus.isVoid) {
19517
- lines.push(`${indent} await ${serviceCall}`);
19518
- lines.push(`${indent} res.status(${op.responseStatus.status}).end()`);
19519
- } else if (op.responseStatus.status === 201) {
19520
- lines.push(`${indent} res.status(201).json(await ${serviceCall})`);
19813
+ lines.push(`${indent} await ${serviceCall}`);
19814
+ lines.push(`${indent} res.status(${op.responseStatus.status}).end()`);
19815
+ } else if (op.responseStatus.isMultiStatus === true) {
19816
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19817
+ lines.push(`${indent} res.status(_envelope.status).json(_envelope.body)`);
19818
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19819
+ if (op.responseStatus.status === 200) {
19820
+ lines.push(`${indent} res.type('text/plain').send(await ${serviceCall})`);
19821
+ } else {
19822
+ lines.push(
19823
+ `${indent} res.status(${op.responseStatus.status}).type('text/plain').send(await ${serviceCall})`
19824
+ );
19825
+ }
19826
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19827
+ if (op.responseStatus.status === 200) {
19828
+ lines.push(
19829
+ `${indent} res.setHeader('Content-Type', 'application/octet-stream').send(Buffer.from(await ${serviceCall}))`
19830
+ );
19831
+ } else {
19832
+ lines.push(
19833
+ `${indent} res.status(${op.responseStatus.status}).setHeader('Content-Type', 'application/octet-stream').send(Buffer.from(await ${serviceCall}))`
19834
+ );
19835
+ }
19836
+ } else if (op.responseStatus.status === 200) {
19837
+ lines.push(`${indent} res.json(await ${serviceCall})`);
19521
19838
  } else {
19522
- lines.push(`${indent} res.json(await ${serviceCall})`);
19523
- }
19839
+ lines.push(`${indent} res.status(${op.responseStatus.status}).json(await ${serviceCall})`);
19840
+ }
19841
+ lines.push(`${indent} } catch (err) {`);
19842
+ lines.push(`${indent} if (err instanceof HttpError) {`);
19843
+ lines.push(`${indent} return void res.status(err.status).json({ error: err.message })`);
19844
+ lines.push(`${indent} }`);
19845
+ lines.push(`${indent} throw err`);
19846
+ lines.push(`${indent} }`);
19524
19847
  lines.push(`${indent}})`);
19525
19848
  return lines.join("\n");
19526
19849
  }
@@ -19528,14 +19851,23 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19528
19851
  const lines = [];
19529
19852
  const genericParts = [];
19530
19853
  if (op.queryParams.length > 0) {
19531
- const queryFields = op.queryParams.map((q) => {
19532
- if (q.tsType === "number") return `${q.name}?: number`;
19533
- if (q.tsType === "boolean") return `${q.name}?: boolean`;
19534
- return `${q.name}?: string`;
19535
- }).join("; ");
19536
- genericParts.push(`Querystring: { ${queryFields} }`);
19537
- }
19538
- if (op.bodyInfo !== void 0 && op.bodyInfo.typeName !== void 0) {
19854
+ const hasDeepOrDelimited = op.queryParams.some(
19855
+ (q) => q.isDeepObject === true || q.delimiterStyle !== void 0
19856
+ );
19857
+ let querystringType;
19858
+ if (hasDeepOrDelimited) {
19859
+ querystringType = "Record<string, string | string[] | undefined>";
19860
+ } else {
19861
+ const queryFields = op.queryParams.map((q) => {
19862
+ if (q.tsType === "number") return `${q.name}?: number`;
19863
+ if (q.tsType === "boolean") return `${q.name}?: boolean`;
19864
+ return `${q.name}?: string`;
19865
+ }).join("; ");
19866
+ querystringType = `{ ${queryFields} }`;
19867
+ }
19868
+ genericParts.push(`Querystring: ${querystringType}`);
19869
+ }
19870
+ if (op.bodyInfo !== void 0 && op.bodyInfo.typeName !== void 0 && !op.bodyInfo.isSynthesized) {
19539
19871
  genericParts.push(`Body: ${op.bodyInfo.typeName}`);
19540
19872
  } else if (op.bodyInfo !== void 0) {
19541
19873
  genericParts.push("Body: unknown");
@@ -19558,7 +19890,36 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19558
19890
  lines.push(`${indent} }`);
19559
19891
  }
19560
19892
  if (op.queryParams.length > 0) {
19561
- const fields = op.queryParams.map((q) => ` ${q.name}: req.query.${q.name}`).join(",\n");
19893
+ const deepObjectParams = op.queryParams.filter((q) => q.isDeepObject === true);
19894
+ const hasDeepOrDelimited = op.queryParams.some(
19895
+ (q) => q.isDeepObject === true || q.delimiterStyle !== void 0
19896
+ );
19897
+ if (hasDeepOrDelimited) {
19898
+ lines.push(
19899
+ `${indent} const _dq = req.query as unknown as Record<string, string | undefined>`
19900
+ );
19901
+ }
19902
+ if (deepObjectParams.length > 0) {
19903
+ for (const q of deepObjectParams) {
19904
+ const prefixLen = q.rawName.length + 1;
19905
+ const bracketPrefix = q.rawName + "[";
19906
+ lines.push(`${indent} const ${q.name} = Object.fromEntries(`);
19907
+ lines.push(
19908
+ `${indent} Object.entries(_dq).filter(([k]) => k.startsWith('${bracketPrefix}') && k.endsWith(']')).map(([k, v]) => [k.slice(${prefixLen}, -1), v])`
19909
+ );
19910
+ lines.push(`${indent} )`);
19911
+ }
19912
+ }
19913
+ const fields = op.queryParams.map((q) => {
19914
+ if (q.isDeepObject === true) {
19915
+ return ` ${q.name}`;
19916
+ }
19917
+ if (q.delimiterStyle !== void 0) {
19918
+ const delim = JSON.stringify(delimiterChar(q.delimiterStyle));
19919
+ return ` ${q.name}: typeof _dq['${q.rawName}'] === 'string' ? _dq['${q.rawName}']!.split(${delim}) : undefined`;
19920
+ }
19921
+ return hasDeepOrDelimited ? ` ${q.name}: _dq['${q.rawName}']` : ` ${q.name}: req.query.${q.name}`;
19922
+ }).join(",\n");
19562
19923
  lines.push(`${indent} const params = {`);
19563
19924
  lines.push(fields);
19564
19925
  lines.push(`${indent} }`);
@@ -19583,17 +19944,23 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19583
19944
  }
19584
19945
  let bodyVarName = "req.body";
19585
19946
  if (op.bodyInfo !== void 0) {
19586
- const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19587
- const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19588
- if (useZod) {
19589
- lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19590
- lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19591
- lines.push(`${indent} if (!parseResult.success) {`);
19947
+ if (op.bodyInfo.contentType === "multipart/form-data") {
19592
19948
  lines.push(
19593
- `${indent} return reply.status(422).send({ error: 'Invalid request body', issues: parseResult.error.issues })`
19949
+ `${indent} // multipart/form-data: assumes @fastify/multipart plugin has populated req.body.`
19594
19950
  );
19595
- lines.push(`${indent} }`);
19596
- bodyVarName = "parseResult.data";
19951
+ } else {
19952
+ const schemaName = op.bodyInfo.typeName !== void 0 ? `${op.bodyInfo.typeName}Schema` : void 0;
19953
+ const useZod = schemaName !== void 0 && schemaNames !== void 0 && schemaNames.has(schemaName);
19954
+ if (useZod) {
19955
+ lines.push(`${indent} // Validate request body: returns 422 with Zod issues on failure`);
19956
+ lines.push(`${indent} const parseResult = ${schemaName}.safeParse(req.body)`);
19957
+ lines.push(`${indent} if (!parseResult.success) {`);
19958
+ lines.push(
19959
+ `${indent} return reply.status(422).send({ error: 'Invalid request body', issues: parseResult.error.issues })`
19960
+ );
19961
+ lines.push(`${indent} }`);
19962
+ bodyVarName = "parseResult.data";
19963
+ }
19597
19964
  }
19598
19965
  }
19599
19966
  const serviceArgs = [];
@@ -19607,18 +19974,50 @@ function buildFastifyRouteHandler(op, indent, schemaNames) {
19607
19974
  serviceArgs.push("params");
19608
19975
  }
19609
19976
  const serviceCall = `service.${op.methodName}(${serviceArgs.join(", ")})`;
19977
+ lines.push(`${indent} try {`);
19610
19978
  if (op.responseStatus.isVoid) {
19611
- lines.push(`${indent} await ${serviceCall}`);
19612
- lines.push(`${indent} reply.status(${op.responseStatus.status}).send()`);
19613
- } else if (op.responseStatus.status === 201) {
19614
- lines.push(`${indent} reply.status(201)`);
19615
- lines.push(`${indent} return ${serviceCall}`);
19979
+ lines.push(`${indent} await ${serviceCall}`);
19980
+ lines.push(`${indent} reply.status(${op.responseStatus.status}).send()`);
19981
+ } else if (op.responseStatus.isMultiStatus === true) {
19982
+ lines.push(`${indent} const _envelope = await ${serviceCall}`);
19983
+ lines.push(`${indent} return reply.status(_envelope.status).send(_envelope.body)`);
19984
+ } else if (op.responseStatus.responseContentType === "text/plain") {
19985
+ if (op.responseStatus.status === 200) {
19986
+ lines.push(`${indent} return reply.type('text/plain').send(await ${serviceCall})`);
19987
+ } else {
19988
+ lines.push(`${indent} return reply.status(${op.responseStatus.status}).type('text/plain').send(await ${serviceCall})`);
19989
+ }
19990
+ } else if (op.responseStatus.responseContentType === "application/octet-stream") {
19991
+ if (op.responseStatus.status === 200) {
19992
+ lines.push(`${indent} return reply.type('application/octet-stream').send(Buffer.from(await ${serviceCall}))`);
19993
+ } else {
19994
+ lines.push(`${indent} return reply.status(${op.responseStatus.status}).type('application/octet-stream').send(Buffer.from(await ${serviceCall}))`);
19995
+ }
19996
+ } else if (op.responseStatus.status === 200) {
19997
+ lines.push(`${indent} return ${serviceCall}`);
19616
19998
  } else {
19617
- lines.push(`${indent} return ${serviceCall}`);
19618
- }
19999
+ lines.push(`${indent} reply.status(${op.responseStatus.status})`);
20000
+ lines.push(`${indent} return ${serviceCall}`);
20001
+ }
20002
+ lines.push(`${indent} } catch (err) {`);
20003
+ lines.push(`${indent} if (err instanceof HttpError) {`);
20004
+ lines.push(`${indent} return reply.status(err.status).send({ error: err.message })`);
20005
+ lines.push(`${indent} }`);
20006
+ lines.push(`${indent} throw err`);
20007
+ lines.push(`${indent} }`);
19619
20008
  lines.push(`${indent}})`);
19620
20009
  return lines.join("\n");
19621
20010
  }
20011
+ function httpErrorClassLines() {
20012
+ return [
20013
+ "export class HttpError extends Error {",
20014
+ " constructor(public readonly status: number, message: string) {",
20015
+ " super(message)",
20016
+ " this.name = 'HttpError'",
20017
+ " }",
20018
+ "}"
20019
+ ];
20020
+ }
19622
20021
  function operationsNeedZodForParams(operations) {
19623
20022
  for (const op of operations) {
19624
20023
  if (op.pathParamValidations.length > 0) return true;
@@ -19628,7 +20027,7 @@ function operationsNeedZodForParams(operations) {
19628
20027
  return false;
19629
20028
  }
19630
20029
  function generateExpressRouter(spec, options) {
19631
- const serviceName = deriveServiceName2(spec);
20030
+ const serviceName = deriveServiceName(spec);
19632
20031
  const operations = collectOperations2(spec);
19633
20032
  const { sortedBodyTypes, usedSchemaNames, needsZod } = collectGeneratorSetup(operations, options);
19634
20033
  const lines = [];
@@ -19651,6 +20050,8 @@ function generateExpressRouter(spec, options) {
19651
20050
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19652
20051
  }
19653
20052
  lines.push("");
20053
+ for (const l of httpErrorClassLines()) lines.push(l);
20054
+ lines.push("");
19654
20055
  lines.push(`export function createRouter(service: ${serviceName}): Router {`);
19655
20056
  lines.push(" const router = Router()");
19656
20057
  lines.push("");
@@ -19667,7 +20068,7 @@ function generateExpressRouter(spec, options) {
19667
20068
  };
19668
20069
  }
19669
20070
  function generateFastifyRouter(spec, options) {
19670
- const serviceName = deriveServiceName2(spec);
20071
+ const serviceName = deriveServiceName(spec);
19671
20072
  const operations = collectOperations2(spec);
19672
20073
  const { sortedBodyTypes, usedSchemaNames, needsZod } = collectGeneratorSetup(operations, options);
19673
20074
  const lines = [];
@@ -19686,6 +20087,8 @@ function generateFastifyRouter(spec, options) {
19686
20087
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19687
20088
  }
19688
20089
  lines.push("");
20090
+ for (const l of httpErrorClassLines()) lines.push(l);
20091
+ lines.push("");
19689
20092
  lines.push(`export function createRouter(app: FastifyInstance, service: ${serviceName}): void {`);
19690
20093
  for (const op of operations) {
19691
20094
  lines.push("");
@@ -19699,7 +20102,7 @@ function generateFastifyRouter(spec, options) {
19699
20102
  };
19700
20103
  }
19701
20104
  function generateRouter(spec, options) {
19702
- const serviceName = deriveServiceName2(spec);
20105
+ const serviceName = deriveServiceName(spec);
19703
20106
  const operations = collectOperations2(spec);
19704
20107
  const { sortedBodyTypes, usedSchemaNames, needsZod } = collectGeneratorSetup(operations, options);
19705
20108
  const lines = [];
@@ -19718,6 +20121,8 @@ function generateRouter(spec, options) {
19718
20121
  lines.push(`import { ${sortedUsedSchemas.join(", ")} } from '${options.schemaImportPath}'`);
19719
20122
  }
19720
20123
  lines.push("");
20124
+ for (const l of httpErrorClassLines()) lines.push(l);
20125
+ lines.push("");
19721
20126
  lines.push(`export function createRouter(service: ${serviceName}): Hono {`);
19722
20127
  lines.push(" const app = new Hono()");
19723
20128
  lines.push("");
@@ -19740,63 +20145,63 @@ async function formatTs(content, filePath) {
19740
20145
  const config = await resolveConfig(filePath);
19741
20146
  return format(content, { ...config, parser: "typescript" });
19742
20147
  }
19743
- async function generate2(cwd, configPath) {
19744
- console.log("Loading config...");
19745
- const config = await loadConfig(cwd, configPath);
20148
+ function buildRouterFile(spec, framework, options) {
20149
+ if (framework === "hono") return generateRouter(spec, options);
20150
+ if (framework === "express") return generateExpressRouter(spec, options);
20151
+ return generateFastifyRouter(spec, options);
20152
+ }
20153
+ async function generateOne(cwd, config, label) {
19746
20154
  const inputPath = (0, import_node_path2.resolve)(cwd, config.input_openapi);
19747
20155
  const outputDir = (0, import_node_path2.resolve)(cwd, config.output);
19748
20156
  const framework = config.framework ?? "none";
19749
- console.log(`Parsing spec: ${inputPath}`);
20157
+ const prefix = label !== void 0 ? `[${label}] ` : "";
20158
+ console.log(`${prefix}Parsing spec: ${inputPath}`);
19750
20159
  const spec = await parseSpec(inputPath);
19751
- const generatedFiles = [];
19752
- generatedFiles.push(generateService(spec));
19753
- if (framework === "hono") {
19754
- generatedFiles.push(generateRouter(spec));
19755
- } else if (framework === "express") {
19756
- generatedFiles.push(generateExpressRouter(spec));
19757
- } else if (framework === "fastify") {
19758
- generatedFiles.push(generateFastifyRouter(spec));
19759
- }
19760
- console.log(`Writing output to: ${outputDir}`);
20160
+ const generatedFiles = [generateService(spec)];
20161
+ if (framework !== "none") {
20162
+ generatedFiles.push(buildRouterFile(spec, framework));
20163
+ }
20164
+ console.log(`${prefix}Writing output to: ${outputDir}`);
19761
20165
  await (0, import_promises2.mkdir)(outputDir, { recursive: true });
19762
20166
  for (const file of generatedFiles) {
19763
20167
  const filePath = (0, import_node_path2.join)(outputDir, file.filename);
19764
20168
  await (0, import_promises2.writeFile)(filePath, await formatTs(file.content, filePath), "utf-8");
19765
- console.log(` \u2713 ${file.filename}`);
20169
+ console.log(`${prefix} \u2713 ${file.filename}`);
19766
20170
  }
19767
- if ((framework === "hono" || framework === "express" || framework === "fastify") && config.input_schema !== void 0) {
19768
- const schemaPath = (0, import_node_path2.resolve)(cwd, config.input_schema);
19769
- let schemaContent;
19770
- try {
19771
- schemaContent = await (0, import_promises2.readFile)(schemaPath, "utf-8");
19772
- } catch {
19773
- console.log(` \u2139 input_schema not found at ${schemaPath}, skipping Zod validation`);
19774
- console.log(`Done! Generated ${generatedFiles.length} file(s).`);
19775
- return;
19776
- }
19777
- const exportedSchemas = /* @__PURE__ */ new Set();
19778
- for (const match of schemaContent.matchAll(/^export\s+const\s+(\w+Schema)\b/gm)) {
19779
- exportedSchemas.add(match[1]);
19780
- }
19781
- if (exportedSchemas.size > 0) {
19782
- const relPath = (0, import_node_path2.relative)(outputDir, schemaPath).replace(/\\/g, "/");
19783
- const schemaImportPath = relPath.startsWith(".") ? relPath : `./${relPath}`;
19784
- const schemaImportPathJs = schemaImportPath.replace(/\.ts$/, ".js");
19785
- const routerOptions = { schemaNames: exportedSchemas, schemaImportPath: schemaImportPathJs };
19786
- let routerFile;
19787
- if (framework === "hono") {
19788
- routerFile = generateRouter(spec, routerOptions);
19789
- } else if (framework === "express") {
19790
- routerFile = generateExpressRouter(spec, routerOptions);
19791
- } else {
19792
- routerFile = generateFastifyRouter(spec, routerOptions);
19793
- }
19794
- const routerPath = (0, import_node_path2.join)(outputDir, routerFile.filename);
19795
- await (0, import_promises2.writeFile)(routerPath, await formatTs(routerFile.content, routerPath), "utf-8");
19796
- console.log(` \u2713 router.ts (with Zod validation for ${exportedSchemas.size} schema(s))`);
19797
- }
20171
+ if (framework !== "none" && config.input_schema !== void 0) {
20172
+ await generateSchemaEnhancedRouter(cwd, config, spec, framework, outputDir, prefix);
19798
20173
  }
19799
- console.log(`Done! Generated ${generatedFiles.length} file(s).`);
20174
+ console.log(`${prefix}Done! Generated ${generatedFiles.length} file(s).`);
20175
+ }
20176
+ async function generateSchemaEnhancedRouter(cwd, config, spec, framework, outputDir, prefix) {
20177
+ const schemaPath = (0, import_node_path2.resolve)(cwd, config.input_schema);
20178
+ let schemaContent;
20179
+ try {
20180
+ schemaContent = await (0, import_promises2.readFile)(schemaPath, "utf-8");
20181
+ } catch {
20182
+ console.log(`${prefix} input_schema not found at ${schemaPath}, skipping Zod validation`);
20183
+ return;
20184
+ }
20185
+ const exportedSchemas = /* @__PURE__ */ new Set();
20186
+ for (const match of schemaContent.matchAll(/^export\s+const\s+(\w+Schema)\b/gm)) {
20187
+ exportedSchemas.add(match[1]);
20188
+ }
20189
+ if (exportedSchemas.size === 0) return;
20190
+ const relPath = (0, import_node_path2.relative)(outputDir, schemaPath).replace(/\\/g, "/");
20191
+ const schemaImportPath = relPath.startsWith(".") ? relPath : `./${relPath}`;
20192
+ const schemaImportPathJs = schemaImportPath.replace(/\.ts$/, ".js");
20193
+ const routerFile = buildRouterFile(spec, framework, {
20194
+ schemaNames: exportedSchemas,
20195
+ schemaImportPath: schemaImportPathJs
20196
+ });
20197
+ const routerPath = (0, import_node_path2.join)(outputDir, routerFile.filename);
20198
+ await (0, import_promises2.writeFile)(routerPath, await formatTs(routerFile.content, routerPath), "utf-8");
20199
+ console.log(`${prefix} \u2713 router.ts (with Zod validation for ${exportedSchemas.size} schema(s))`);
20200
+ }
20201
+ async function generate2(cwd, configPath) {
20202
+ console.log("Loading config...");
20203
+ const configs = await loadConfigs2(cwd, configPath);
20204
+ await runProjects(configs, (config, label) => generateOne(cwd, config, label));
19800
20205
  }
19801
20206
 
19802
20207
  // ../openapi-zod-ts/dist/cli-core.js