api-core-lib 16.1.0 → 16.12.133

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 ADDED
@@ -0,0 +1,619 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+
29
+ // node_modules/openapi-types/dist/index.js
30
+ var require_dist = __commonJS({
31
+ "node_modules/openapi-types/dist/index.js"(exports2) {
32
+ "use strict";
33
+ exports2.__esModule = true;
34
+ exports2.OpenAPIV2 = exports2.OpenAPIV3 = void 0;
35
+ var OpenAPIV32;
36
+ (function(OpenAPIV33) {
37
+ var HttpMethods;
38
+ (function(HttpMethods2) {
39
+ HttpMethods2["GET"] = "get";
40
+ HttpMethods2["PUT"] = "put";
41
+ HttpMethods2["POST"] = "post";
42
+ HttpMethods2["DELETE"] = "delete";
43
+ HttpMethods2["OPTIONS"] = "options";
44
+ HttpMethods2["HEAD"] = "head";
45
+ HttpMethods2["PATCH"] = "patch";
46
+ HttpMethods2["TRACE"] = "trace";
47
+ })(HttpMethods = OpenAPIV33.HttpMethods || (OpenAPIV33.HttpMethods = {}));
48
+ })(OpenAPIV32 = exports2.OpenAPIV3 || (exports2.OpenAPIV3 = {}));
49
+ var OpenAPIV2;
50
+ (function(OpenAPIV22) {
51
+ var HttpMethods;
52
+ (function(HttpMethods2) {
53
+ HttpMethods2["GET"] = "get";
54
+ HttpMethods2["PUT"] = "put";
55
+ HttpMethods2["POST"] = "post";
56
+ HttpMethods2["DELETE"] = "delete";
57
+ HttpMethods2["OPTIONS"] = "options";
58
+ HttpMethods2["HEAD"] = "head";
59
+ HttpMethods2["PATCH"] = "patch";
60
+ })(HttpMethods = OpenAPIV22.HttpMethods || (OpenAPIV22.HttpMethods = {}));
61
+ })(OpenAPIV2 = exports2.OpenAPIV2 || (exports2.OpenAPIV2 = {}));
62
+ }
63
+ });
64
+
65
+ // src/cli.ts
66
+ var import_commander = require("commander");
67
+ var import_path2 = __toESM(require("path"), 1);
68
+
69
+ // src/generator/v1.ts
70
+ var import_fs = __toESM(require("fs"), 1);
71
+ var import_path = __toESM(require("path"), 1);
72
+ var import_util = __toESM(require("util"), 1);
73
+ var import_chalk = __toESM(require("chalk"), 1);
74
+ var import_dotenv = __toESM(require("dotenv"), 1);
75
+ var import_swagger_parser = __toESM(require("@apidevtools/swagger-parser"), 1);
76
+ var import_openapi_types = __toESM(require_dist(), 1);
77
+ var import_inquirer = __toESM(require("inquirer"), 1);
78
+
79
+ // src/generator/utils/index.ts
80
+ var getActionName = (opId) => {
81
+ const cleanOpId = opId.replace(/_v\d+$/, "");
82
+ const parts = cleanOpId.split("_");
83
+ if (parts.length > 1) {
84
+ const actionPart = parts.slice(1).join("_");
85
+ return toCamelCase(actionPart);
86
+ }
87
+ return toCamelCase(cleanOpId);
88
+ };
89
+ var toCamelCase = (str) => {
90
+ const s = toPascalCase(str);
91
+ return s.charAt(0).toLowerCase() + s.slice(1);
92
+ };
93
+ var toPascalCase = (str) => str.replace(/[^a-zA-Z0-9_]/g, " ").replace(/(?:^\w|[A-Z]|\b\w)/g, (w) => w.toUpperCase()).replace(/\s+/g, "");
94
+
95
+ // src/generator/core/_propToZod.ts
96
+ function _propToZod(prop) {
97
+ let zodChain;
98
+ const getMsg = (key) => `{ "message": "validation.${key}" }`;
99
+ switch (prop.type) {
100
+ case "string":
101
+ if (prop.enumName) {
102
+ zodChain = `z.enum(${prop.enumName})`;
103
+ } else {
104
+ zodChain = "z.string()";
105
+ if (prop.isRequired) {
106
+ zodChain += `.min(1, ${getMsg("string.nonempty")})`;
107
+ }
108
+ if (prop.maxLength !== void 0) zodChain += `.max(${prop.maxLength}, ${getMsg("string.max")})`;
109
+ if (prop.pattern) zodChain += `.regex(/${prop.pattern}/, ${getMsg("string.regex")})`;
110
+ if (prop.format === "email") zodChain += `.email(${getMsg("string.email")})`;
111
+ if (prop.format === "url") zodChain += `.url(${getMsg("string.url")})`;
112
+ if (prop.format === "uuid") zodChain += `.uuid(${getMsg("string.uuid")})`;
113
+ if (prop.format === "datetime") zodChain += `.datetime(${getMsg("string.datetime")})`;
114
+ }
115
+ break;
116
+ case "integer":
117
+ zodChain = `z.number().int(${getMsg("number.integer")})`;
118
+ if (prop.minimum !== void 0) zodChain += `.min(${prop.minimum}, ${getMsg("number.min")})`;
119
+ if (prop.maximum !== void 0) zodChain += `.max(${prop.maximum}, ${getMsg("number.max")})`;
120
+ break;
121
+ case "number":
122
+ zodChain = `z.number()`;
123
+ if (prop.minimum !== void 0) zodChain += `.min(${prop.minimum}, ${getMsg("number.min")})`;
124
+ if (prop.maximum !== void 0) zodChain += `.max(${prop.maximum}, ${getMsg("number.max")})`;
125
+ break;
126
+ case "boolean":
127
+ zodChain = `z.boolean()`;
128
+ break;
129
+ case "array":
130
+ const itemSchema = prop.items ? _propToZod(prop.items) : "z.any()";
131
+ zodChain = `z.array(${itemSchema})`;
132
+ if (prop.minItems !== void 0) zodChain += `.min(${prop.minItems}, ${getMsg("array.min")})`;
133
+ if (prop.maxItems !== void 0) zodChain += `.max(${prop.maxItems}, ${getMsg("array.max")})`;
134
+ break;
135
+ case "object":
136
+ if (prop.properties && prop.properties.length > 0) {
137
+ const shape = prop.properties.map((p) => ` ${p.name}: ${_propToZod(p)}`).join(",\n");
138
+ zodChain = `z.object({
139
+ ${shape}
140
+ })`;
141
+ } else {
142
+ zodChain = "z.record(z.unknown())";
143
+ }
144
+ break;
145
+ default:
146
+ zodChain = "z.any()";
147
+ break;
148
+ }
149
+ if (prop.description) {
150
+ zodChain += `.describe(${JSON.stringify(prop.description)})`;
151
+ }
152
+ if (!prop.isRequired) {
153
+ zodChain += ".optional()";
154
+ }
155
+ if (prop.isNullable) {
156
+ zodChain += ".nullable()";
157
+ }
158
+ return zodChain;
159
+ }
160
+
161
+ // src/generator/core/_propToMock.ts
162
+ function _propToMock(prop) {
163
+ if (prop.example) return prop.example;
164
+ if (prop.name.match(/image|avatar|logo|url/i)) return "https://via.placeholder.com/150";
165
+ if (prop.enum) return prop.enum[0];
166
+ switch (prop.type) {
167
+ case "string":
168
+ if (prop.format === "email") return "test@example.com";
169
+ if (prop.format === "uuid") return "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11";
170
+ return `Mock ${toPascalCase(prop.name)}`;
171
+ case "integer":
172
+ case "number":
173
+ return 1;
174
+ case "boolean":
175
+ return true;
176
+ case "array":
177
+ return prop.items ? [_propToMock(prop.items)] : [];
178
+ case "object":
179
+ const mock = {};
180
+ if (prop.properties) prop.properties.forEach((p) => {
181
+ mock[p.name] = _propToMock(p);
182
+ });
183
+ return mock;
184
+ default:
185
+ return null;
186
+ }
187
+ }
188
+
189
+ // src/generator/v1.ts
190
+ var DEBUG_MODE = process.env.DEBUG === "true";
191
+ var debugLog = (title, data) => DEBUG_MODE && console.log(import_chalk.default.yellow(`
192
+ [DEBUG: ${title}]`), import_util.default.inspect(data, { depth: 5, colors: true }));
193
+ var toCamelCase2 = (str) => str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase());
194
+ var toPascalCase2 = (str) => str.replace(/(?:^|[-_\s])(\w)/g, (_, c) => c.toUpperCase());
195
+ var sanitizeForModuleName = (name) => toPascalCase2(name.replace(/api/gi, "").replace(/[^a-zA-Z0-9\s-]/g, ""));
196
+ function findCommonPath(paths) {
197
+ if (!paths || paths.length === 0) return "/";
198
+ const sortedPaths = paths.sort();
199
+ const first = sortedPaths[0].split("/");
200
+ const last = sortedPaths[sortedPaths.length - 1].split("/");
201
+ let i = 0;
202
+ while (i < first.length && first[i] === last[i]) i++;
203
+ return first.slice(0, i).join("/") || "/";
204
+ }
205
+ function parseSchema(name, schema, allEnums) {
206
+ const properties = [];
207
+ const enums = {};
208
+ if (schema.properties) {
209
+ for (const propName in schema.properties) {
210
+ const propSchema = schema.properties[propName];
211
+ let itemSchema, generatedEnumName;
212
+ if (propSchema.type === "array" && propSchema.items) {
213
+ const itemTypeName = `${toPascalCase2(name)}${toPascalCase2(propName)}Item`;
214
+ itemSchema = parseSchema(itemTypeName, propSchema.items, allEnums).properties[0];
215
+ }
216
+ if (propSchema.enum) {
217
+ const enumName = `${toPascalCase2(name)}${toPascalCase2(propName)}Enum`;
218
+ enums[propName] = propSchema.enum;
219
+ if (!allEnums.has(enumName)) {
220
+ allEnums.set(enumName, { name: enumName, values: propSchema.enum });
221
+ }
222
+ generatedEnumName = enumName;
223
+ }
224
+ properties.push({ name: propName, type: propSchema.type || "object", isRequired: (schema.required || []).includes(propName), isNullable: propSchema.nullable || false, description: propSchema.description, example: propSchema.example, enum: propSchema.enum, enumName: generatedEnumName, format: propSchema.format, items: itemSchema, properties: propSchema.properties ? parseSchema(`${name}${toPascalCase2(propName)}`, propSchema, allEnums).properties : void 0, minLength: propSchema.minLength, maxLength: propSchema.maxLength, pattern: propSchema.pattern, minimum: propSchema.minimum, maximum: propSchema.maximum, minItems: propSchema.minItems, maxItems: propSchema.maxItems });
225
+ }
226
+ }
227
+ return { name, description: schema.description, properties, enums };
228
+ }
229
+ function parseSpecToModules(spec) {
230
+ const modules = /* @__PURE__ */ new Map(), allSchemas = /* @__PURE__ */ new Map(), allEnums = /* @__PURE__ */ new Map(), modulePaths = /* @__PURE__ */ new Map();
231
+ const BUILT_IN_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "void", "undefined", "unknown", "any", "Date", "Promise", "QueryOptions", "Record<string, any>", "Record<string, unknown>"]);
232
+ const registerSchema = (schema, baseName) => {
233
+ if (!schema) return "Record<string, any>";
234
+ if (schema.type === "array" && schema.items) {
235
+ const itemSchema = schema.items, itemBaseName = `${baseName}Item`;
236
+ if (itemSchema.properties || itemSchema.type === "object") return `${registerSchema(itemSchema, itemBaseName)}[]`;
237
+ return `${itemSchema.type === "integer" ? "number" : itemSchema.type || "any"}[]`;
238
+ }
239
+ if (schema.type === "object" || schema.properties || schema.allOf || !schema.type) {
240
+ if (!schema.properties && !schema.allOf) return "Record<string, any>";
241
+ const typeName = toPascalCase2(baseName.replace(/_v\d+(Request|Response)$/, "$1"));
242
+ if (!allSchemas.has(typeName)) allSchemas.set(typeName, parseSchema(typeName, schema, allEnums));
243
+ return typeName;
244
+ }
245
+ return schema.type === "integer" ? "number" : schema.type || "any";
246
+ };
247
+ for (const apiPath in spec.paths) {
248
+ const pathItem = spec.paths[apiPath];
249
+ if (!pathItem) continue;
250
+ for (const method in pathItem) {
251
+ if (!Object.values(import_openapi_types.OpenAPIV3.HttpMethods).includes(method)) continue;
252
+ const endpoint = pathItem[method];
253
+ if (!endpoint.tags || endpoint.tags.length === 0 || !endpoint.operationId) continue;
254
+ const moduleName = sanitizeForModuleName(endpoint.tags[0]);
255
+ if (!modules.has(moduleName)) {
256
+ modules.set(moduleName, { moduleName, baseEndpoint: "", actions: {}, schemas: /* @__PURE__ */ new Set(), enums: /* @__PURE__ */ new Set() });
257
+ modulePaths.set(moduleName, []);
258
+ }
259
+ const currentModule = modules.get(moduleName);
260
+ modulePaths.get(moduleName).push(apiPath);
261
+ const successKey = Object.keys(endpoint.responses).find((c) => c.startsWith("2"));
262
+ const successRes = successKey ? endpoint.responses[successKey] : void 0;
263
+ const outputType = successKey === "204" ? "void" : registerSchema(successRes?.content?.["application/json"]?.schema, `${endpoint.operationId}Response`);
264
+ const reqBody = endpoint.requestBody;
265
+ let inputType = "undefined";
266
+ if (reqBody?.content?.["application/json"]?.schema) inputType = registerSchema(reqBody.content["application/json"].schema, `${endpoint.operationId}Request`);
267
+ else if ((endpoint.parameters || []).some((p) => p.in === "query")) inputType = "QueryOptions";
268
+ [inputType, outputType].forEach((t) => {
269
+ const cleanType = t.replace("[]", "");
270
+ if (cleanType && !BUILT_IN_TYPES.has(cleanType)) {
271
+ currentModule.schemas.add(cleanType);
272
+ const schemaDef = allSchemas.get(cleanType);
273
+ if (schemaDef) Object.keys(schemaDef.enums).forEach((propName) => currentModule.enums.add(`${toPascalCase2(cleanType)}${toPascalCase2(propName)}Enum`));
274
+ }
275
+ });
276
+ currentModule.actions[getActionName(endpoint.operationId)] = { name: getActionName(endpoint.operationId), method: method.toUpperCase(), path: apiPath, description: endpoint.summary || "", hasQuery: (endpoint.parameters || []).some((p) => p.in === "query"), autoFetch: method.toUpperCase() === "GET" && !apiPath.includes("{"), requiresAuth: !!endpoint.security && endpoint.security.length > 0, inputType, outputType, pathParams: (apiPath.match(/{(\w+)}/g) || []).map((p) => p.slice(1, -1)) };
277
+ }
278
+ }
279
+ modules.forEach((mod, name) => {
280
+ mod.baseEndpoint = findCommonPath(modulePaths.get(name));
281
+ Object.values(mod.actions).forEach((action) => action.path = action.path.replace(mod.baseEndpoint, "").replace(/^\//, "") || "/");
282
+ });
283
+ debugLog("Final Parsed Modules", Object.fromEntries(modules));
284
+ return { modules, allSchemas, allEnums };
285
+ }
286
+ async function generateModuleFiles(module2, allSchemas, allEnums, outputDir) {
287
+ const moduleOutputPath = import_path.default.join(outputDir, module2.moduleName);
288
+ if (!import_fs.default.existsSync(moduleOutputPath)) import_fs.default.mkdirSync(moduleOutputPath, { recursive: true });
289
+ console.log(import_chalk.default.cyan(`
290
+ Generating module: ${import_chalk.default.bold(module2.moduleName)}`));
291
+ const BUILT_IN_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "void", "undefined", "unknown", "any", "Date", "Promise", "QueryOptions", "Record<string, any>", "Record<string, unknown>"]);
292
+ const schemasToImport = [...module2.schemas].filter((name) => !BUILT_IN_TYPES.has(name)).sort();
293
+ const enumsToImport = [...module2.enums].sort();
294
+ const createdFileExports = ["config"];
295
+ let configContent = `// Auto-generated file. Do not edit.
296
+
297
+ import type { ApiModuleConfig, ActionConfigModule, QueryOptions } from 'api-core-lib';
298
+ `;
299
+ if (schemasToImport.length > 0) configContent += `import type { ${schemasToImport.join(", ")} } from './types';
300
+ `;
301
+ const actionsType = Object.values(module2.actions).map((a) => ` ${a.name}: ActionConfigModule<${a.inputType}, ${a.outputType}>;`).join("\n");
302
+ const actionsValue = Object.values(module2.actions).map((a) => {
303
+ const { inputType, outputType, name, ...c } = a;
304
+ return ` ${name}: ${JSON.stringify(c, null, 2).replace(/\n/g, "\n ")}`;
305
+ }).join(",\n");
306
+ configContent += `
307
+ export const ${module2.moduleName}: ApiModuleConfig<{
308
+ ${actionsType}
309
+ }> = {
310
+ baseEndpoint: '${module2.baseEndpoint}',
311
+ actions: {
312
+ ${actionsValue}
313
+ },
314
+ };
315
+ `;
316
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "config.ts"), configContent.trim());
317
+ console.log(import_chalk.default.gray(` \u2713 config.ts`));
318
+ if (schemasToImport.length > 0) {
319
+ if (enumsToImport.length > 0) {
320
+ let enumsContent = `// Auto-generated file. Do not edit.
321
+
322
+ `;
323
+ enumsToImport.forEach((enumName) => {
324
+ const enumDef = allEnums.get(enumName);
325
+ if (enumDef) {
326
+ enumsContent += `export const ${enumName} = ${JSON.stringify(enumDef.values)} as const;
327
+ `;
328
+ enumsContent += `export type ${enumName} = typeof ${enumName}[number];
329
+
330
+ `;
331
+ }
332
+ });
333
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "enums.ts"), enumsContent.trim());
334
+ console.log(import_chalk.default.gray(` \u2713 enums.ts`));
335
+ createdFileExports.push("enums");
336
+ }
337
+ let typesContent = `// Auto-generated file. Do not edit.
338
+
339
+ `;
340
+ if (enumsToImport.length > 0) typesContent += `import type { ${enumsToImport.join(", ")} } from './enums';
341
+
342
+ `;
343
+ schemasToImport.forEach((typeName) => {
344
+ const parsedSchema = allSchemas.get(typeName);
345
+ if (parsedSchema) {
346
+ if (parsedSchema.description) typesContent += `/** ${parsedSchema.description} */
347
+ `;
348
+ typesContent += `export interface ${typeName} {
349
+ `;
350
+ parsedSchema.properties.forEach((prop) => {
351
+ if (prop.description) typesContent += ` /** ${prop.description} */
352
+ `;
353
+ let propType = prop.enumName || (prop.items ? `${prop.items.type || "any"}[]` : prop.type);
354
+ if (propType === "integer") propType = "number";
355
+ if (propType === "object") propType = "Record<string, any>";
356
+ typesContent += ` ${prop.name}${prop.isRequired ? "" : "?"}: ${propType};
357
+ `;
358
+ });
359
+ typesContent += `}
360
+
361
+ `;
362
+ }
363
+ });
364
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "types.ts"), typesContent.trim());
365
+ console.log(import_chalk.default.gray(` \u2713 types.ts`));
366
+ createdFileExports.push("types");
367
+ let validationContent = `// Auto-generated file. Do not edit.
368
+
369
+ import { z } from 'zod';
370
+ `;
371
+ if (enumsToImport.length > 0) validationContent += `import { ${enumsToImport.join(", ")} } from './enums';
372
+ `;
373
+ schemasToImport.forEach((typeName) => {
374
+ const parsedSchema = allSchemas.get(typeName);
375
+ if (parsedSchema) {
376
+ const zodShape = parsedSchema.properties.map((p) => ` ${p.name}: ${_propToZod(p)}`).join(",\n");
377
+ validationContent += `
378
+ /** Zod schema for {@link ${typeName}}. */
379
+ `;
380
+ validationContent += `export const ${typeName}Schema = z.object({
381
+ ${zodShape}
382
+ });
383
+
384
+ `;
385
+ validationContent += `export type ${typeName}Validated = z.infer<typeof ${typeName}Schema>;
386
+ `;
387
+ }
388
+ });
389
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "validation.ts"), validationContent.trim());
390
+ console.log(import_chalk.default.gray(` \u2713 validation.ts`));
391
+ createdFileExports.push("validation");
392
+ let mocksContent = `// Auto-generated file. Do not edit.
393
+ import type { ${schemasToImport.join(", ")} } from './types';
394
+ `;
395
+ if (enumsToImport.length > 0) mocksContent += `import { ${enumsToImport.join(", ")} } from './enums';
396
+
397
+ `;
398
+ schemasToImport.forEach((typeName) => {
399
+ const parsedSchema = allSchemas.get(typeName);
400
+ if (parsedSchema) {
401
+ const mockObject = {};
402
+ parsedSchema.properties.forEach((p) => {
403
+ mockObject[p.name] = _propToMock(p);
404
+ });
405
+ mocksContent += `export const mock${typeName}: ${typeName} = ${JSON.stringify(mockObject, null, 2)};
406
+
407
+ `;
408
+ }
409
+ });
410
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "mocks.ts"), mocksContent.trim());
411
+ console.log(import_chalk.default.gray(` \u2713 mocks.ts`));
412
+ createdFileExports.push("mocks");
413
+ }
414
+ const indexFilePath = import_path.default.join(moduleOutputPath, "index.ts");
415
+ const initialIndexContent = `// Auto-generated file. Do not edit.
416
+ ` + createdFileExports.map((e) => `export * from './${e}';`).join("\n");
417
+ import_fs.default.writeFileSync(indexFilePath, initialIndexContent);
418
+ console.log(import_chalk.default.gray(` \u2713 index.ts (Initial Entry Point)`));
419
+ const moduleBaseName = module2.moduleName.replace(/Api$/, "");
420
+ const camelCaseModuleName = toCamelCase2(moduleBaseName);
421
+ let endpointsContent = `// Auto-generated file. Do not edit.
422
+
423
+ `;
424
+ endpointsContent += `import type { QueryOptions } from 'api-core-lib';
425
+ `;
426
+ const endpointTypesToImport = /* @__PURE__ */ new Set();
427
+ Object.values(module2.actions).forEach((action) => {
428
+ if (action.inputType !== "undefined" && action.inputType !== "QueryOptions" && !BUILT_IN_TYPES.has(action.inputType)) {
429
+ endpointTypesToImport.add(action.inputType);
430
+ }
431
+ });
432
+ if (endpointTypesToImport.size > 0) {
433
+ endpointsContent += `import type { ${[...endpointTypesToImport].join(", ")} } from './types';
434
+
435
+ `;
436
+ }
437
+ endpointsContent += `export const ${camelCaseModuleName}Endpoints = {
438
+ `;
439
+ for (const action of Object.values(module2.actions)) {
440
+ const params = [];
441
+ if (action.pathParams.length > 0) params.push(`params: { ${action.pathParams.map((p) => `${p}: string | number`).join("; ")} }`);
442
+ if (action.inputType !== "undefined" && action.inputType !== "QueryOptions") params.push(`body: ${action.inputType}`);
443
+ if (action.hasQuery) params.push(`query?: QueryOptions`);
444
+ endpointsContent += ` /**
445
+ * ${action.method} ${module2.baseEndpoint}${action.path}
446
+ * ${action.description}
447
+ */
448
+ `;
449
+ endpointsContent += ` ${action.name}: (${params.join(", ")}) => ({
450
+ `;
451
+ endpointsContent += ` action: '${action.name}' as const,
452
+ `;
453
+ if (action.pathParams.length > 0) endpointsContent += ` pathParams: params,
454
+ `;
455
+ if (action.inputType !== "undefined" && action.inputType !== "QueryOptions") endpointsContent += ` input: body,
456
+ `;
457
+ else if (action.hasQuery) endpointsContent += ` input: query,
458
+ `;
459
+ endpointsContent += ` }),
460
+
461
+ `;
462
+ }
463
+ endpointsContent += `};
464
+ `;
465
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, `${camelCaseModuleName}.endpoints.ts`), endpointsContent.trim());
466
+ console.log(import_chalk.default.gray(` \u2713 ${camelCaseModuleName}.endpoints.ts`));
467
+ const contextFileContent = `// Auto-generated file. Do not edit.
468
+ 'use client';
469
+
470
+ import React from 'react';
471
+ import { createApiModuleContext, useApiModule } from 'api-core-lib/client';
472
+ import { apiClient } from '@/lib/api-core/clientApi';
473
+ import { ${module2.moduleName}, ${module2.moduleName} as TModuleType } from './config';
474
+
475
+ const { Provider, useContext: use${moduleBaseName}Context } = createApiModuleContext<typeof TModuleType['actions']>();
476
+
477
+ export { use${moduleBaseName}Context };
478
+
479
+ type Options = Parameters<typeof useApiModule>[2];
480
+
481
+ interface ${moduleBaseName}ProviderProps {
482
+ children: React.ReactNode;
483
+ options?: Options;
484
+ }
485
+
486
+ export function ${moduleBaseName}Provider({ children, options = {} }: ${moduleBaseName}ProviderProps) {
487
+ const api = useApiModule(apiClient, ${module2.moduleName}, options);
488
+ return <Provider value={api}>{children}</Provider>;
489
+ }`;
490
+ const contextFileName = `${camelCaseModuleName}.context.tsx`;
491
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, contextFileName), contextFileContent.trim());
492
+ console.log(import_chalk.default.gray(` \u2713 ${contextFileName}`));
493
+ const serverHelperContent = `// Auto-generated file. For server-side use only.
494
+
495
+ import { createServerApi } from 'api-core-lib/server';
496
+ import { serverApiClient } from '@/lib/api-core/serverApi';
497
+ import { ${module2.moduleName} } from './config';
498
+
499
+ export const create${moduleBaseName}ServerApi = () => {
500
+ return createServerApi(serverApiClient, ${module2.moduleName});
501
+ };`;
502
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, `${camelCaseModuleName}.server.ts`), serverHelperContent.trim());
503
+ console.log(import_chalk.default.gray(` \u2713 ${camelCaseModuleName}.server.ts`));
504
+ const clientExports = `
505
+ export * from './${camelCaseModuleName}.endpoints';
506
+ export * from './${camelCaseModuleName}.context';`;
507
+ import_fs.default.appendFileSync(indexFilePath, clientExports);
508
+ console.log(import_chalk.default.gray(` \u2713 index.ts (Updated)`));
509
+ const serverEntryPointContent = `// Auto-generated file. For server-side use only.
510
+
511
+ export * from './${camelCaseModuleName}.server';
512
+ export * from './${camelCaseModuleName}.endpoints';`;
513
+ import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "server.ts"), serverEntryPointContent);
514
+ console.log(import_chalk.default.gray(` \u2713 server.ts (Server Entry Point)`));
515
+ }
516
+ async function runGenerator(options) {
517
+ console.log(import_chalk.default.cyan.bold("\u{1F680} Starting API Development Platform Generator..."));
518
+ import_dotenv.default.config({ path: options.envPath });
519
+ const specUrl = process.env.OPENAPI_SPEC_URL || "./swagger.json";
520
+ const prompt = import_inquirer.default.default?.prompt || import_inquirer.default.prompt;
521
+ try {
522
+ console.log(import_chalk.default.blue(`
523
+ \u23F3 Step 1: Dereferencing spec from ${specUrl}...`));
524
+ const spec = await import_swagger_parser.default.dereference(specUrl);
525
+ console.log(import_chalk.default.green("\u2713 Spec fully dereferenced."));
526
+ console.log(import_chalk.default.blue("\n\u23F3 Step 2: Parsing spec with intelligent grouping..."));
527
+ const { modules, allSchemas, allEnums } = parseSpecToModules(spec);
528
+ const newModuleNames = Array.from(modules.keys());
529
+ console.log(import_chalk.default.green(`\u2713 Found and grouped ${modules.size} logical modules from the spec.`));
530
+ const outputDir = options.output;
531
+ if (import_fs.default.existsSync(outputDir)) {
532
+ console.log(import_chalk.default.yellow.bold(`
533
+ \u26A0\uFE0F An existing generation has been found at: ${outputDir}`));
534
+ const { action } = await prompt([{
535
+ type: "list",
536
+ name: "action",
537
+ message: "What would you like to do?",
538
+ choices: [
539
+ { name: "Perform a Selective Update (Recommended)", value: "selective" },
540
+ { name: "Full Refresh (Deletes everything and regenerates)", value: "full" },
541
+ { name: "Cancel", value: "cancel" }
542
+ ]
543
+ }]);
544
+ if (action === "cancel") {
545
+ console.log(import_chalk.default.gray("Operation cancelled."));
546
+ return;
547
+ }
548
+ if (action === "full") {
549
+ const { confirm } = await prompt([{
550
+ type: "confirm",
551
+ name: "confirm",
552
+ message: import_chalk.default.red.bold("Are you sure? This will permanently delete all files in the output directory."),
553
+ default: false
554
+ }]);
555
+ if (!confirm) {
556
+ console.log(import_chalk.default.gray("Full refresh cancelled."));
557
+ return;
558
+ }
559
+ console.log(import_chalk.default.yellow(`\u{1F525} Deleting existing directory...`));
560
+ import_fs.default.rmSync(outputDir, { recursive: true, force: true });
561
+ } else if (action === "selective") {
562
+ const existingModuleDirs = import_fs.default.readdirSync(outputDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
563
+ const newModules = newModuleNames.filter((name) => !existingModuleDirs.includes(name));
564
+ const existingModulesToUpdate = newModuleNames.filter((name) => existingModuleDirs.includes(name));
565
+ const obsoleteModules = existingModuleDirs.filter((name) => !newModuleNames.includes(name));
566
+ console.log(import_chalk.default.blue("\n--- Analysis Complete ---"));
567
+ if (newModules.length > 0) console.log(import_chalk.default.green(` + Found ${newModules.length} new module(s): ${newModules.join(", ")}`));
568
+ if (obsoleteModules.length > 0) console.log(import_chalk.default.yellow(` - Found ${obsoleteModules.length} obsolete module(s) (will not be touched): ${obsoleteModules.join(", ")}`));
569
+ console.log(import_chalk.default.cyan(` ~ Found ${existingModulesToUpdate.length} existing module(s) available for update.`));
570
+ console.log("-------------------------\n");
571
+ if (newModuleNames.length === 0) {
572
+ console.log(import_chalk.default.yellow("No modules found in the spec to update. Exiting."));
573
+ return;
574
+ }
575
+ const { modulesToRegenerate } = await prompt([{
576
+ type: "checkbox",
577
+ name: "modulesToRegenerate",
578
+ message: "Select the modules to generate or update:",
579
+ choices: [
580
+ ...newModules.map((name) => ({ name: `${name} (New)`, value: name, checked: true })),
581
+ ...existingModulesToUpdate.map((name) => ({ name, value: name, checked: false }))
582
+ ]
583
+ }]);
584
+ if (!modulesToRegenerate || modulesToRegenerate.length === 0) {
585
+ console.log(import_chalk.default.gray("No modules selected. Operation cancelled."));
586
+ return;
587
+ }
588
+ console.log(import_chalk.default.blue("\n\u23F3 Step 3: Generating selected module files..."));
589
+ for (const moduleName of modulesToRegenerate) {
590
+ const moduleData = modules.get(moduleName);
591
+ if (moduleData) await generateModuleFiles(moduleData, allSchemas, allEnums, outputDir);
592
+ }
593
+ console.log(import_chalk.default.green("\n\u2713 Selective update complete."));
594
+ console.log(import_chalk.default.bold.green("\n\u{1F389} Your development platform is synchronized!"));
595
+ return;
596
+ }
597
+ }
598
+ console.log(import_chalk.default.blue("\n\u23F3 Step 3: Performing a full generation..."));
599
+ for (const module2 of modules.values()) {
600
+ await generateModuleFiles(module2, allSchemas, allEnums, options.output);
601
+ }
602
+ console.log(import_chalk.default.green("\n\u2713 All module files generated successfully."));
603
+ console.log(import_chalk.default.bold.green("\n\u{1F389} API generation complete! Your development platform is ready."));
604
+ console.log(import_chalk.default.bold.cyan(` Output directory: ${options.output}`));
605
+ } catch (error) {
606
+ console.error(import_chalk.default.red.bold("\n\u274C An error occurred during generation:"));
607
+ console.error(import_chalk.default.red(`Error Message: ${error.message}`));
608
+ if (error.stack && DEBUG_MODE) console.error(import_chalk.default.gray(error.stack));
609
+ process.exit(1);
610
+ }
611
+ }
612
+
613
+ // src/cli.ts
614
+ console.log("API Core Lib - Code Generator");
615
+ import_commander.program.option("-o, --output <path>", "Output directory for generated files", "src/lib/api-generated").option("--env-path <path>", ".env file path", import_path2.default.resolve(process.cwd(), ".env")).action((options) => {
616
+ const absoluteOutputPath = import_path2.default.resolve(process.cwd(), options.output);
617
+ runGenerator({ ...options, output: absoluteOutputPath });
618
+ });
619
+ import_commander.program.parse(process.argv);