@cushin/api-codegen 5.0.7 → 6.0.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.js CHANGED
@@ -28,6 +28,7 @@ async function loadConfig(configPath) {
28
28
  const rootDir = path.dirname(result.filepath);
29
29
  const endpointsPath = path.resolve(rootDir, userConfig.endpoints);
30
30
  const outputDir = path.resolve(rootDir, userConfig.output);
31
+ const swaggerSourcePath = userConfig.swaggerSource ? path.resolve(rootDir, userConfig.swaggerSource) : void 0;
31
32
  const generateHooks = userConfig.generateHooks ?? true;
32
33
  const generateServerActions = userConfig.generateServerActions ?? userConfig.provider === "nextjs";
33
34
  const generateServerQueries = userConfig.generateServerQueries ?? userConfig.provider === "nextjs";
@@ -38,6 +39,7 @@ async function loadConfig(configPath) {
38
39
  rootDir,
39
40
  endpointsPath,
40
41
  outputDir,
42
+ swaggerSourcePath,
41
43
  generateHooks,
42
44
  generateServerActions,
43
45
  generateServerQueries,
@@ -564,7 +566,7 @@ ${this.generateApiClientMethods()}
564
566
  };
565
567
 
566
568
  /**
567
- * Initialize API client with auth callbacks
569
+ * Initialize API client with auth callbacks and optional global headers
568
570
  * Call this function in your auth provider setup
569
571
  *
570
572
  * @example
@@ -576,10 +578,16 @@ ${this.generateApiClientMethods()}
576
578
  * },
577
579
  * };
578
580
  *
579
- * initializeAPIClient(authCallbacks);
581
+ * initializeAPIClient(authCallbacks, {
582
+ * 'X-Client-ID': 'my-app',
583
+ * 'X-Environment': 'production',
584
+ * });
580
585
  */
581
- export const initializeAPIClient = (authCallbacks: AuthCallbacks) => {
582
- baseClient = createAPIClient(apiConfig, authCallbacks) as any;
586
+ export const initializeAPIClient = (
587
+ authCallbacks: AuthCallbacks,
588
+ globalHeaders?: Record<string, string>,
589
+ ) => {
590
+ baseClient = createAPIClient(apiConfig, authCallbacks, globalHeaders) as any;
583
591
  return baseClient;
584
592
  };
585
593
 
@@ -612,7 +620,7 @@ export let serverClient: APIClientMethods & {
612
620
  serverClient = createAPIClient(apiConfig) as any;
613
621
 
614
622
  /**
615
- * Initialize server-side API client with auth callbacks
623
+ * Initialize server-side API client with auth callbacks and optional global headers
616
624
  * Call this in Server Actions or Route Handlers where you need authentication
617
625
  *
618
626
  * @example
@@ -629,10 +637,16 @@ serverClient = createAPIClient(apiConfig) as any;
629
637
  * }
630
638
  * };
631
639
  *
632
- * initializeServerClient(authCallbacks);
640
+ * initializeServerClient(authCallbacks, {
641
+ * 'X-Server-ID': 'server-1',
642
+ * 'X-Request-ID': requestId,
643
+ * });
633
644
  */
634
- export const initializeServerClient = (authCallbacks: AuthCallbacks) => {
635
- serverClient = createAPIClient(apiConfig, authCallbacks) as any;
645
+ export const initializeServerClient = (
646
+ authCallbacks: AuthCallbacks,
647
+ globalHeaders?: Record<string, string>,
648
+ ) => {
649
+ serverClient = createAPIClient(apiConfig, authCallbacks, globalHeaders) as any;
636
650
  return serverClient;
637
651
  };
638
652
 
@@ -999,11 +1013,576 @@ var CodeGenerator = class {
999
1013
 
1000
1014
  // src/core/codegen.ts
1001
1015
  import { fileURLToPath } from "url";
1016
+ import fs11 from "fs/promises";
1017
+ import path12 from "path";
1018
+
1019
+ // src/swagger/parser.ts
1020
+ import fs10 from "fs";
1021
+ import path11 from "path";
1022
+ async function parseOpenAPISpec(filePath) {
1023
+ const content = await fs10.promises.readFile(filePath, "utf-8");
1024
+ const ext = path11.extname(filePath).toLowerCase();
1025
+ let spec;
1026
+ if (ext === ".json") {
1027
+ spec = JSON.parse(content);
1028
+ } else if (ext === ".yaml" || ext === ".yml") {
1029
+ const yaml = await import("yaml");
1030
+ spec = yaml.parse(content);
1031
+ } else {
1032
+ throw new Error(
1033
+ `Unsupported file format: ${ext}. Only .json, .yaml, and .yml are supported.`
1034
+ );
1035
+ }
1036
+ if (!spec.openapi || !spec.openapi.startsWith("3.")) {
1037
+ throw new Error(
1038
+ `Unsupported OpenAPI version: ${spec.openapi}. Only OpenAPI 3.0 and 3.1 are supported.`
1039
+ );
1040
+ }
1041
+ return spec;
1042
+ }
1043
+ function extractEndpoints(spec) {
1044
+ const endpoints = [];
1045
+ for (const [path14, pathItem] of Object.entries(spec.paths)) {
1046
+ const methods = [
1047
+ "get",
1048
+ "post",
1049
+ "put",
1050
+ "delete",
1051
+ "patch",
1052
+ "head",
1053
+ "options"
1054
+ ];
1055
+ for (const method of methods) {
1056
+ const operation = pathItem[method];
1057
+ if (!operation) continue;
1058
+ const endpoint = parseOperation(
1059
+ path14,
1060
+ method,
1061
+ operation,
1062
+ pathItem,
1063
+ spec
1064
+ );
1065
+ endpoints.push(endpoint);
1066
+ }
1067
+ }
1068
+ return endpoints;
1069
+ }
1070
+ function parseOperation(path14, method, operation, pathItem, spec) {
1071
+ const allParameters = [
1072
+ ...pathItem.parameters || [],
1073
+ ...operation.parameters || []
1074
+ ];
1075
+ const pathParams = [];
1076
+ const queryParams = [];
1077
+ for (const param of allParameters) {
1078
+ const resolved = resolveParameter(param, spec);
1079
+ const parsed = {
1080
+ name: resolved.name,
1081
+ type: schemaToTypeString(resolved.schema),
1082
+ required: resolved.required ?? resolved.in === "path",
1083
+ description: resolved.description,
1084
+ schema: resolved.schema
1085
+ // Keep original schema
1086
+ };
1087
+ if (resolved.in === "path") {
1088
+ pathParams.push(parsed);
1089
+ } else if (resolved.in === "query") {
1090
+ queryParams.push(parsed);
1091
+ }
1092
+ }
1093
+ let requestBody;
1094
+ if (operation.requestBody) {
1095
+ const resolved = resolveRequestBody(operation.requestBody, spec);
1096
+ const content = resolved.content?.["application/json"];
1097
+ if (content) {
1098
+ requestBody = {
1099
+ type: schemaToTypeString(content.schema),
1100
+ required: resolved.required ?? false,
1101
+ description: resolved.description,
1102
+ schema: content.schema
1103
+ };
1104
+ }
1105
+ }
1106
+ let response;
1107
+ const responses = operation.responses;
1108
+ const successStatus = responses["200"] || responses["201"] || responses["204"];
1109
+ if (successStatus) {
1110
+ const statusCode = responses["200"] ? "200" : responses["201"] ? "201" : "204";
1111
+ const resolved = resolveResponse(successStatus, spec);
1112
+ const content = resolved.content?.["application/json"];
1113
+ if (content || statusCode === "204") {
1114
+ response = {
1115
+ statusCode,
1116
+ type: content ? schemaToTypeString(content.schema) : "void",
1117
+ description: resolved.description,
1118
+ schema: content?.schema || { type: "null" }
1119
+ };
1120
+ }
1121
+ }
1122
+ if (!response) {
1123
+ for (const [statusCode, res] of Object.entries(responses)) {
1124
+ if (statusCode.startsWith("2")) {
1125
+ const resolved = resolveResponse(res, spec);
1126
+ const content = resolved.content?.["application/json"];
1127
+ response = {
1128
+ statusCode,
1129
+ type: content ? schemaToTypeString(content.schema) : "void",
1130
+ description: resolved.description,
1131
+ schema: content?.schema || { type: "null" }
1132
+ };
1133
+ break;
1134
+ }
1135
+ }
1136
+ }
1137
+ return {
1138
+ path: path14,
1139
+ method: method.toUpperCase(),
1140
+ operationId: operation.operationId,
1141
+ summary: operation.summary,
1142
+ description: operation.description,
1143
+ tags: operation.tags,
1144
+ pathParams: pathParams.length > 0 ? pathParams : void 0,
1145
+ queryParams: queryParams.length > 0 ? queryParams : void 0,
1146
+ requestBody,
1147
+ response
1148
+ };
1149
+ }
1150
+ function resolveParameter(param, spec) {
1151
+ if ("$ref" in param && param.$ref) {
1152
+ const refPath = param.$ref.replace("#/components/parameters/", "");
1153
+ const resolved = spec.components?.parameters?.[refPath];
1154
+ if (!resolved) {
1155
+ throw new Error(`Cannot resolve parameter reference: ${param.$ref}`);
1156
+ }
1157
+ return resolved;
1158
+ }
1159
+ return param;
1160
+ }
1161
+ function resolveRequestBody(requestBody, spec) {
1162
+ if ("$ref" in requestBody && requestBody.$ref) {
1163
+ const refPath = requestBody.$ref.replace("#/components/requestBodies/", "");
1164
+ const resolved = spec.components?.requestBodies?.[refPath];
1165
+ if (!resolved) {
1166
+ throw new Error(`Cannot resolve request body reference: ${requestBody.$ref}`);
1167
+ }
1168
+ return resolved;
1169
+ }
1170
+ return requestBody;
1171
+ }
1172
+ function resolveResponse(response, spec) {
1173
+ if ("$ref" in response && response.$ref) {
1174
+ const refPath = response.$ref.replace("#/components/responses/", "");
1175
+ const resolved = spec.components?.responses?.[refPath];
1176
+ if (!resolved) {
1177
+ throw new Error(`Cannot resolve response reference: ${response.$ref}`);
1178
+ }
1179
+ return resolved;
1180
+ }
1181
+ return response;
1182
+ }
1183
+ function schemaToTypeString(schema) {
1184
+ if (!schema) return "any";
1185
+ if (schema.$ref) {
1186
+ const parts = schema.$ref.split("/");
1187
+ return parts[parts.length - 1];
1188
+ }
1189
+ if (schema.type) {
1190
+ switch (schema.type) {
1191
+ case "string":
1192
+ return "string";
1193
+ case "number":
1194
+ case "integer":
1195
+ return "number";
1196
+ case "boolean":
1197
+ return "boolean";
1198
+ case "array":
1199
+ if (schema.items) {
1200
+ return `${schemaToTypeString(schema.items)}[]`;
1201
+ }
1202
+ return "any[]";
1203
+ case "object":
1204
+ return "object";
1205
+ default:
1206
+ return "any";
1207
+ }
1208
+ }
1209
+ if (schema.allOf) {
1210
+ return schema.allOf.map(schemaToTypeString).join(" & ");
1211
+ }
1212
+ if (schema.oneOf || schema.anyOf) {
1213
+ const schemas = schema.oneOf || schema.anyOf;
1214
+ return schemas.map(schemaToTypeString).join(" | ");
1215
+ }
1216
+ return "any";
1217
+ }
1218
+
1219
+ // src/swagger/config-generator.ts
1220
+ function groupEndpointsByTags(endpoints) {
1221
+ const grouped = /* @__PURE__ */ new Map();
1222
+ for (const endpoint of endpoints) {
1223
+ const tag = endpoint.tags && endpoint.tags.length > 0 ? endpoint.tags[0] : "default";
1224
+ if (!grouped.has(tag)) {
1225
+ grouped.set(tag, []);
1226
+ }
1227
+ grouped.get(tag).push(endpoint);
1228
+ }
1229
+ return grouped;
1230
+ }
1231
+ function generateConfigFile(endpoints, spec, baseUrl) {
1232
+ const lines = [];
1233
+ lines.push('import { defineConfig, defineEndpoint } from "@cushin/api-runtime";');
1234
+ lines.push('import { z } from "zod";');
1235
+ lines.push("");
1236
+ if (spec.components?.schemas) {
1237
+ lines.push("// Schema definitions from OpenAPI components");
1238
+ for (const [name, schema] of Object.entries(spec.components.schemas)) {
1239
+ const zodSchema = convertSchemaToZod(schema, spec);
1240
+ lines.push(`const ${name}Schema = ${zodSchema};`);
1241
+ }
1242
+ lines.push("");
1243
+ }
1244
+ lines.push("// Endpoint definitions");
1245
+ const endpointNames = [];
1246
+ for (const endpoint of endpoints) {
1247
+ const name = generateEndpointName(endpoint);
1248
+ endpointNames.push(name);
1249
+ const path14 = convertPathFormat(endpoint.path);
1250
+ lines.push(`const ${name} = defineEndpoint({`);
1251
+ lines.push(` path: "${path14}",`);
1252
+ lines.push(` method: "${endpoint.method}",`);
1253
+ if (endpoint.pathParams && endpoint.pathParams.length > 0) {
1254
+ const paramsSchema = generateParamsSchema(endpoint.pathParams, spec);
1255
+ lines.push(` params: ${paramsSchema},`);
1256
+ }
1257
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
1258
+ const querySchema = generateQuerySchema(endpoint.queryParams, spec);
1259
+ lines.push(` query: ${querySchema},`);
1260
+ }
1261
+ if (endpoint.requestBody) {
1262
+ const bodySchema = convertSchemaToZod(endpoint.requestBody.schema, spec);
1263
+ lines.push(` body: ${bodySchema},`);
1264
+ }
1265
+ if (endpoint.response) {
1266
+ const responseSchema = convertSchemaToZod(endpoint.response.schema, spec);
1267
+ lines.push(` response: ${responseSchema},`);
1268
+ } else {
1269
+ lines.push(` response: z.any(),`);
1270
+ }
1271
+ if (endpoint.tags && endpoint.tags.length > 0) {
1272
+ const tagsStr = endpoint.tags.map((t) => `"${t}"`).join(", ");
1273
+ lines.push(` tags: [${tagsStr}],`);
1274
+ }
1275
+ if (endpoint.description || endpoint.summary) {
1276
+ const desc = endpoint.description || endpoint.summary;
1277
+ lines.push(` description: "${escapeString(desc)}",`);
1278
+ }
1279
+ lines.push("});");
1280
+ lines.push("");
1281
+ }
1282
+ lines.push("// API Configuration");
1283
+ lines.push("export const apiConfig = defineConfig({");
1284
+ if (baseUrl) {
1285
+ lines.push(` baseUrl: "${baseUrl}",`);
1286
+ } else if (spec.servers && spec.servers.length > 0) {
1287
+ lines.push(` baseUrl: "${spec.servers[0].url}",`);
1288
+ }
1289
+ lines.push(" endpoints: {");
1290
+ for (const name of endpointNames) {
1291
+ lines.push(` ${name},`);
1292
+ }
1293
+ lines.push(" },");
1294
+ lines.push("});");
1295
+ lines.push("");
1296
+ return lines.join("\n");
1297
+ }
1298
+ function generateEndpointName(endpoint) {
1299
+ if (endpoint.operationId) {
1300
+ return toCamelCase(endpoint.operationId);
1301
+ }
1302
+ const method = endpoint.method.toLowerCase();
1303
+ const pathParts = endpoint.path.split("/").filter((p) => p && !p.startsWith(":")).map((p) => p.replace(/[^a-zA-Z0-9]/g, ""));
1304
+ const parts = [method, ...pathParts];
1305
+ return toCamelCase(parts.join("_"));
1306
+ }
1307
+ function toCamelCase(str) {
1308
+ return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase());
1309
+ }
1310
+ function generateParamsSchema(params, spec) {
1311
+ const props = [];
1312
+ for (const param of params) {
1313
+ const zodType = convertSchemaToZod(param.schema, spec);
1314
+ props.push(` ${param.name}: ${zodType}`);
1315
+ }
1316
+ return `z.object({
1317
+ ${props.join(",\n")}
1318
+ })`;
1319
+ }
1320
+ function generateQuerySchema(params, spec) {
1321
+ const props = [];
1322
+ for (const param of params) {
1323
+ let zodType = convertSchemaToZod(param.schema, spec);
1324
+ if (!param.required) {
1325
+ zodType += ".optional()";
1326
+ }
1327
+ props.push(` ${param.name}: ${zodType}`);
1328
+ }
1329
+ return `z.object({
1330
+ ${props.join(",\n")}
1331
+ })`;
1332
+ }
1333
+ function convertSchemaToZod(schema, spec) {
1334
+ if (!schema) return "z.any()";
1335
+ if (schema.$ref) {
1336
+ const refName = schema.$ref.split("/").pop();
1337
+ if (refName && spec.components?.schemas?.[refName]) {
1338
+ return `${refName}Schema`;
1339
+ }
1340
+ return "z.any()";
1341
+ }
1342
+ if (schema.type) {
1343
+ switch (schema.type) {
1344
+ case "string":
1345
+ if (schema.enum) {
1346
+ const values = schema.enum.map((v) => `"${v}"`).join(", ");
1347
+ return `z.enum([${values}])`;
1348
+ }
1349
+ return "z.string()";
1350
+ case "number":
1351
+ case "integer":
1352
+ return "z.number()";
1353
+ case "boolean":
1354
+ return "z.boolean()";
1355
+ case "array":
1356
+ if (schema.items) {
1357
+ const itemSchema = convertSchemaToZod(schema.items, spec);
1358
+ return `z.array(${itemSchema})`;
1359
+ }
1360
+ return "z.array(z.any())";
1361
+ case "object":
1362
+ if (schema.properties) {
1363
+ const props = [];
1364
+ const required = schema.required || [];
1365
+ for (const [propName, propSchema] of Object.entries(
1366
+ schema.properties
1367
+ )) {
1368
+ let zodType = convertSchemaToZod(propSchema, spec);
1369
+ if (!required.includes(propName)) {
1370
+ zodType += ".optional()";
1371
+ }
1372
+ props.push(` ${propName}: ${zodType}`);
1373
+ }
1374
+ return `z.object({
1375
+ ${props.join(",\n")}
1376
+ })`;
1377
+ }
1378
+ return "z.object({})";
1379
+ case "null":
1380
+ return "z.null()";
1381
+ default:
1382
+ return "z.any()";
1383
+ }
1384
+ }
1385
+ if (schema.allOf) {
1386
+ const schemas = schema.allOf.map((s) => convertSchemaToZod(s, spec));
1387
+ return schemas.join(".and(");
1388
+ }
1389
+ if (schema.oneOf || schema.anyOf) {
1390
+ const schemas = (schema.oneOf || schema.anyOf).map(
1391
+ (s) => convertSchemaToZod(s, spec)
1392
+ );
1393
+ return `z.union([${schemas.join(", ")}])`;
1394
+ }
1395
+ return "z.any()";
1396
+ }
1397
+ function escapeString(str) {
1398
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
1399
+ }
1400
+ function convertPathFormat(path14) {
1401
+ return path14.replace(/\{([^}]+)\}/g, ":$1");
1402
+ }
1403
+ function generateModuleFile(tag, endpoints, spec) {
1404
+ const lines = [];
1405
+ lines.push('import { defineEndpoint } from "@cushin/api-runtime";');
1406
+ lines.push('import { z } from "zod";');
1407
+ lines.push("");
1408
+ const usedSchemas = /* @__PURE__ */ new Set();
1409
+ for (const endpoint of endpoints) {
1410
+ collectUsedSchemas(endpoint, usedSchemas, spec);
1411
+ }
1412
+ if (usedSchemas.size > 0 && spec.components?.schemas) {
1413
+ lines.push("// Schema definitions");
1414
+ const sortedSchemas = sortSchemasByDependencies(Array.from(usedSchemas), spec);
1415
+ for (const schemaName of sortedSchemas) {
1416
+ const schema = spec.components.schemas[schemaName];
1417
+ if (schema) {
1418
+ const zodSchema = convertSchemaToZod(schema, spec);
1419
+ lines.push(`const ${schemaName}Schema = ${zodSchema};`);
1420
+ }
1421
+ }
1422
+ lines.push("");
1423
+ }
1424
+ lines.push("// Endpoint definitions");
1425
+ const endpointNames = [];
1426
+ for (const endpoint of endpoints) {
1427
+ const name = generateEndpointName(endpoint);
1428
+ endpointNames.push(name);
1429
+ const path14 = convertPathFormat(endpoint.path);
1430
+ lines.push(`export const ${name} = defineEndpoint({`);
1431
+ lines.push(` path: "${path14}",`);
1432
+ lines.push(` method: "${endpoint.method}",`);
1433
+ if (endpoint.pathParams && endpoint.pathParams.length > 0) {
1434
+ const paramsSchema = generateParamsSchema(endpoint.pathParams, spec);
1435
+ lines.push(` params: ${paramsSchema},`);
1436
+ }
1437
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
1438
+ const querySchema = generateQuerySchema(endpoint.queryParams, spec);
1439
+ lines.push(` query: ${querySchema},`);
1440
+ }
1441
+ if (endpoint.requestBody) {
1442
+ const bodySchema = convertSchemaToZod(endpoint.requestBody.schema, spec);
1443
+ lines.push(` body: ${bodySchema},`);
1444
+ }
1445
+ if (endpoint.response) {
1446
+ const responseSchema = convertSchemaToZod(endpoint.response.schema, spec);
1447
+ lines.push(` response: ${responseSchema},`);
1448
+ } else {
1449
+ lines.push(` response: z.any(),`);
1450
+ }
1451
+ if (endpoint.tags && endpoint.tags.length > 0) {
1452
+ const tagsStr = endpoint.tags.map((t) => `"${t}"`).join(", ");
1453
+ lines.push(` tags: [${tagsStr}],`);
1454
+ }
1455
+ if (endpoint.description || endpoint.summary) {
1456
+ const desc = endpoint.description || endpoint.summary;
1457
+ lines.push(` description: "${escapeString(desc)}",`);
1458
+ }
1459
+ lines.push("});");
1460
+ lines.push("");
1461
+ }
1462
+ return lines.join("\n");
1463
+ }
1464
+ function sortSchemasByDependencies(schemaNames, spec) {
1465
+ const sorted = [];
1466
+ const visited = /* @__PURE__ */ new Set();
1467
+ const visiting = /* @__PURE__ */ new Set();
1468
+ function visit(name) {
1469
+ if (visited.has(name)) return;
1470
+ if (visiting.has(name)) {
1471
+ return;
1472
+ }
1473
+ visiting.add(name);
1474
+ const schema = spec.components?.schemas?.[name];
1475
+ if (schema) {
1476
+ const deps = /* @__PURE__ */ new Set();
1477
+ extractSchemaNames(schema, deps, spec);
1478
+ for (const dep of deps) {
1479
+ if (dep !== name && schemaNames.includes(dep)) {
1480
+ visit(dep);
1481
+ }
1482
+ }
1483
+ }
1484
+ visiting.delete(name);
1485
+ if (!visited.has(name)) {
1486
+ visited.add(name);
1487
+ sorted.push(name);
1488
+ }
1489
+ }
1490
+ for (const name of schemaNames) {
1491
+ visit(name);
1492
+ }
1493
+ return sorted;
1494
+ }
1495
+ function collectUsedSchemas(endpoint, usedSchemas, spec) {
1496
+ if (endpoint.requestBody?.schema) {
1497
+ extractSchemaNames(endpoint.requestBody.schema, usedSchemas, spec);
1498
+ }
1499
+ if (endpoint.response?.schema) {
1500
+ extractSchemaNames(endpoint.response.schema, usedSchemas, spec);
1501
+ }
1502
+ if (endpoint.pathParams) {
1503
+ for (const param of endpoint.pathParams) {
1504
+ extractSchemaNames(param.schema, usedSchemas, spec);
1505
+ }
1506
+ }
1507
+ if (endpoint.queryParams) {
1508
+ for (const param of endpoint.queryParams) {
1509
+ extractSchemaNames(param.schema, usedSchemas, spec);
1510
+ }
1511
+ }
1512
+ }
1513
+ function extractSchemaNames(schema, names, spec) {
1514
+ if (!schema) return;
1515
+ if (schema.$ref) {
1516
+ const schemaName = schema.$ref.split("/").pop();
1517
+ if (schemaName) {
1518
+ names.add(schemaName);
1519
+ const referencedSchema = spec.components?.schemas?.[schemaName];
1520
+ if (referencedSchema) {
1521
+ extractSchemaNames(referencedSchema, names, spec);
1522
+ }
1523
+ }
1524
+ return;
1525
+ }
1526
+ if (schema.properties) {
1527
+ for (const prop of Object.values(schema.properties)) {
1528
+ extractSchemaNames(prop, names, spec);
1529
+ }
1530
+ }
1531
+ if (schema.items) {
1532
+ extractSchemaNames(schema.items, names, spec);
1533
+ }
1534
+ if (schema.allOf) {
1535
+ for (const s of schema.allOf) {
1536
+ extractSchemaNames(s, names, spec);
1537
+ }
1538
+ }
1539
+ if (schema.oneOf) {
1540
+ for (const s of schema.oneOf) {
1541
+ extractSchemaNames(s, names, spec);
1542
+ }
1543
+ }
1544
+ if (schema.anyOf) {
1545
+ for (const s of schema.anyOf) {
1546
+ extractSchemaNames(s, names, spec);
1547
+ }
1548
+ }
1549
+ }
1550
+ function generateIndexFile(tagModules, spec, baseUrl) {
1551
+ const lines = [];
1552
+ lines.push('import { defineConfig } from "@cushin/api-runtime";');
1553
+ lines.push("");
1554
+ for (const [tag] of tagModules) {
1555
+ const moduleFileName = tag.toLowerCase().replace(/[^a-z0-9]/g, "-");
1556
+ lines.push(`import * as ${toCamelCase(tag)}Module from "./${moduleFileName}.js";`);
1557
+ }
1558
+ lines.push("");
1559
+ lines.push("export const apiConfig = defineConfig({");
1560
+ const url = baseUrl || (spec.servers && spec.servers.length > 0 ? spec.servers[0].url : void 0);
1561
+ if (url) {
1562
+ lines.push(` baseUrl: "${url}",`);
1563
+ }
1564
+ lines.push(" endpoints: {");
1565
+ for (const [tag] of tagModules) {
1566
+ lines.push(` ...${toCamelCase(tag)}Module,`);
1567
+ }
1568
+ lines.push(" },");
1569
+ lines.push("});");
1570
+ lines.push("");
1571
+ for (const [tag] of tagModules) {
1572
+ lines.push(`export * from "./${tag.toLowerCase().replace(/[^a-z0-9]/g, "-")}.js";`);
1573
+ }
1574
+ return lines.join("\n");
1575
+ }
1576
+
1577
+ // src/core/codegen.ts
1002
1578
  var CodegenCore = class {
1003
1579
  constructor(config) {
1004
1580
  this.config = config;
1005
1581
  }
1006
1582
  async execute() {
1583
+ if (this.config.swaggerSourcePath) {
1584
+ await this.generateConfigFromSwagger();
1585
+ }
1007
1586
  const apiConfig = await this.loadAPIConfig();
1008
1587
  this.config.apiConfig = apiConfig;
1009
1588
  const generator = new CodeGenerator({
@@ -1012,6 +1591,67 @@ var CodegenCore = class {
1012
1591
  });
1013
1592
  await generator.generate();
1014
1593
  }
1594
+ /**
1595
+ * Generate single config file (no split)
1596
+ */
1597
+ async generateSingleConfigFile(endpoints, spec) {
1598
+ const configContent = generateConfigFile(endpoints, spec, this.config.baseUrl);
1599
+ const endpointsDir = path12.dirname(this.config.endpointsPath);
1600
+ await fs11.mkdir(endpointsDir, { recursive: true });
1601
+ await fs11.writeFile(this.config.endpointsPath, configContent, "utf-8");
1602
+ console.log(`\u2713 Generated endpoint config at ${this.config.endpointsPath}`);
1603
+ }
1604
+ /**
1605
+ * Generate multiple module files split by tags
1606
+ */
1607
+ async generateMultipleModuleFiles(endpoints, spec) {
1608
+ const grouped = groupEndpointsByTags(endpoints);
1609
+ console.log(`\u2713 Grouped into ${grouped.size} modules by tags`);
1610
+ const endpointsDir = path12.dirname(this.config.endpointsPath);
1611
+ const modulesDir = path12.join(endpointsDir, "modules");
1612
+ await fs11.mkdir(modulesDir, { recursive: true });
1613
+ for (const [tag, tagEndpoints] of grouped.entries()) {
1614
+ const moduleFileName = tag.toLowerCase().replace(/[^a-z0-9]/g, "-");
1615
+ const moduleFilePath = path12.join(modulesDir, `${moduleFileName}.ts`);
1616
+ const moduleContent = generateModuleFile(tag, tagEndpoints, spec);
1617
+ await fs11.writeFile(moduleFilePath, moduleContent, "utf-8");
1618
+ console.log(` \u2713 ${tag}: ${tagEndpoints.length} endpoints \u2192 ${moduleFileName}.ts`);
1619
+ }
1620
+ const indexContent = generateIndexFile(grouped, spec, this.config.baseUrl);
1621
+ const indexPath = path12.join(modulesDir, "index.ts");
1622
+ await fs11.writeFile(indexPath, indexContent, "utf-8");
1623
+ console.log(`\u2713 Generated index.ts at ${modulesDir}/index.ts`);
1624
+ const mainExportContent = `export * from "./modules/index.js";
1625
+ `;
1626
+ await fs11.mkdir(endpointsDir, { recursive: true });
1627
+ await fs11.writeFile(this.config.endpointsPath, mainExportContent, "utf-8");
1628
+ console.log(`\u2713 Generated main export at ${this.config.endpointsPath}`);
1629
+ }
1630
+ /**
1631
+ * Generate endpoint config file from Swagger/OpenAPI spec
1632
+ */
1633
+ async generateConfigFromSwagger() {
1634
+ if (!this.config.swaggerSourcePath) {
1635
+ return;
1636
+ }
1637
+ try {
1638
+ console.log(`\u{1F4C4} Parsing Swagger spec from ${this.config.swaggerSourcePath}...`);
1639
+ const spec = await parseOpenAPISpec(this.config.swaggerSourcePath);
1640
+ console.log(`\u2713 Found OpenAPI ${spec.openapi} specification`);
1641
+ console.log(` Title: ${spec.info.title} (v${spec.info.version})`);
1642
+ const endpoints = extractEndpoints(spec);
1643
+ console.log(`\u2713 Extracted ${endpoints.length} endpoints`);
1644
+ if (this.config.splitByTags) {
1645
+ await this.generateMultipleModuleFiles(endpoints, spec);
1646
+ } else {
1647
+ await this.generateSingleConfigFile(endpoints, spec);
1648
+ }
1649
+ } catch (error) {
1650
+ throw new Error(
1651
+ `Failed to generate config from Swagger: ${error instanceof Error ? error.message : String(error)}`
1652
+ );
1653
+ }
1654
+ }
1015
1655
  async loadAPIConfig() {
1016
1656
  try {
1017
1657
  const jiti = createJiti(fileURLToPath(import.meta.url), {
@@ -1037,14 +1677,14 @@ var CodegenCore = class {
1037
1677
  };
1038
1678
 
1039
1679
  // src/cli.ts
1040
- import path11 from "path";
1680
+ import path13 from "path";
1041
1681
  var program = new Command();
1042
1682
  setupCLIProgram(program);
1043
1683
  var context = {
1044
1684
  loadConfig,
1045
1685
  validateConfig,
1046
1686
  CodegenCore,
1047
- pathToFileURL: (filePath) => new URL(`file://${path11.resolve(filePath)}`)
1687
+ pathToFileURL: (filePath) => new URL(`file://${path13.resolve(filePath)}`)
1048
1688
  };
1049
1689
  setupGenerateCommand(program, context);
1050
1690
  setupInitCommand(program);