api-core-lib 12.0.72 → 12.0.73
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 +88 -94
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -76,14 +76,14 @@ var import_swagger_parser = __toESM(require("@apidevtools/swagger-parser"), 1);
|
|
|
76
76
|
var import_openapi_types = __toESM(require_dist(), 1);
|
|
77
77
|
var DEBUG_MODE = process.env.DEBUG === "true";
|
|
78
78
|
var debugLog = (title, data) => DEBUG_MODE && console.log(import_chalk.default.yellow(`
|
|
79
|
-
[DEBUG: ${title}]`), import_util.default.inspect(data, { depth:
|
|
79
|
+
[DEBUG: ${title}]`), import_util.default.inspect(data, { depth: 5, colors: true }));
|
|
80
80
|
var toPascalCase = (str) => str.replace(/[^a-zA-Z0-9_]/g, " ").replace(/(?:^\w|[A-Z]|\b\w)/g, (w) => w.toUpperCase()).replace(/\s+/g, "");
|
|
81
81
|
var toCamelCase = (str) => {
|
|
82
82
|
const s = toPascalCase(str);
|
|
83
83
|
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
84
84
|
};
|
|
85
|
-
var
|
|
86
|
-
var getActionName = (opId) => toCamelCase(opId.
|
|
85
|
+
var sanitizeForModuleName = (tagName) => `${toPascalCase(tagName.replace(/[^a-zA-Z0-9]/g, " "))}Api`;
|
|
86
|
+
var getActionName = (opId) => toCamelCase(opId.replace(/^(central|tenant)/i, "").replace(/_v\d+$/, ""));
|
|
87
87
|
var findCommonPath = (paths) => {
|
|
88
88
|
if (!paths || paths.length === 0) return "/";
|
|
89
89
|
const sorted = [...paths].sort();
|
|
@@ -94,14 +94,21 @@ var findCommonPath = (paths) => {
|
|
|
94
94
|
const prefix = first.substring(0, i);
|
|
95
95
|
return prefix.substring(0, prefix.lastIndexOf("/") + 1) || "/";
|
|
96
96
|
};
|
|
97
|
-
function parseSchema(name, schema) {
|
|
97
|
+
function parseSchema(name, schema, allEnums) {
|
|
98
98
|
const properties = [];
|
|
99
|
+
const enums = {};
|
|
99
100
|
if (schema.properties) {
|
|
100
101
|
for (const propName in schema.properties) {
|
|
101
102
|
const propSchema = schema.properties[propName];
|
|
102
|
-
let
|
|
103
|
+
let itemSchema;
|
|
103
104
|
if (propSchema.type === "array" && propSchema.items) {
|
|
104
|
-
|
|
105
|
+
const itemTypeName = `${toPascalCase(name)}${toPascalCase(propName)}Item`;
|
|
106
|
+
itemSchema = parseSchema(itemTypeName, propSchema.items, allEnums).properties[0];
|
|
107
|
+
}
|
|
108
|
+
if (propSchema.enum) {
|
|
109
|
+
const enumName = `${toPascalCase(name)}${toPascalCase(propName)}Enum`;
|
|
110
|
+
enums[propName] = propSchema.enum;
|
|
111
|
+
if (!allEnums.has(enumName)) allEnums.set(enumName, { name: enumName, values: propSchema.enum });
|
|
105
112
|
}
|
|
106
113
|
properties.push({
|
|
107
114
|
name: propName,
|
|
@@ -112,24 +119,29 @@ function parseSchema(name, schema) {
|
|
|
112
119
|
example: propSchema.example,
|
|
113
120
|
enum: propSchema.enum,
|
|
114
121
|
format: propSchema.format,
|
|
115
|
-
items,
|
|
116
|
-
|
|
117
|
-
properties: propSchema.properties ? parseSchema("sub-object", propSchema).properties : void 0
|
|
122
|
+
items: itemSchema,
|
|
123
|
+
properties: propSchema.properties ? parseSchema(`${name}${toPascalCase(propName)}`, propSchema, allEnums).properties : void 0
|
|
118
124
|
});
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
|
-
return { name, description: schema.description, properties };
|
|
127
|
+
return { name, description: schema.description, properties, enums };
|
|
122
128
|
}
|
|
123
129
|
function parseSpecToModules(spec) {
|
|
124
130
|
const modules = /* @__PURE__ */ new Map();
|
|
125
131
|
const allSchemas = /* @__PURE__ */ new Map();
|
|
132
|
+
const allEnums = /* @__PURE__ */ new Map();
|
|
126
133
|
const modulePaths = /* @__PURE__ */ new Map();
|
|
127
134
|
const registerSchema = (schema, baseName) => {
|
|
128
135
|
if (!schema) return "unknown";
|
|
129
|
-
if (schema.type === "array" && schema.items)
|
|
136
|
+
if (schema.type === "array" && schema.items) {
|
|
137
|
+
const itemSchema = schema.items;
|
|
138
|
+
const itemBaseName = `${baseName}Item`;
|
|
139
|
+
if (itemSchema.properties || itemSchema.type === "object") return `${registerSchema(itemSchema, itemBaseName)}[]`;
|
|
140
|
+
return `${itemSchema.type || "unknown"}[]`;
|
|
141
|
+
}
|
|
130
142
|
if (schema.type === "object" || schema.properties || schema.allOf || !schema.type) {
|
|
131
143
|
const typeName = toPascalCase(baseName.replace(/_v\d+(Request|Response)$/, "$1"));
|
|
132
|
-
if (!allSchemas.has(typeName)) allSchemas.set(typeName, parseSchema(typeName, schema));
|
|
144
|
+
if (!allSchemas.has(typeName)) allSchemas.set(typeName, parseSchema(typeName, schema, allEnums));
|
|
133
145
|
return typeName;
|
|
134
146
|
}
|
|
135
147
|
return schema.type === "integer" ? "number" : schema.type || "unknown";
|
|
@@ -140,10 +152,11 @@ function parseSpecToModules(spec) {
|
|
|
140
152
|
for (const method in pathItem) {
|
|
141
153
|
if (!Object.values(import_openapi_types.OpenAPIV3.HttpMethods).includes(method)) continue;
|
|
142
154
|
const endpoint = pathItem[method];
|
|
143
|
-
if (!endpoint.operationId) continue;
|
|
144
|
-
const
|
|
155
|
+
if (!endpoint.tags || endpoint.tags.length === 0 || !endpoint.operationId) continue;
|
|
156
|
+
const tagName = endpoint.tags[0];
|
|
157
|
+
const moduleName = sanitizeForModuleName(tagName);
|
|
145
158
|
if (!modules.has(moduleName)) {
|
|
146
|
-
modules.set(moduleName, { moduleName, baseEndpoint: "", actions: {}, schemas: /* @__PURE__ */ new Set() });
|
|
159
|
+
modules.set(moduleName, { moduleName, baseEndpoint: "", actions: {}, schemas: /* @__PURE__ */ new Set(), enums: /* @__PURE__ */ new Set() });
|
|
147
160
|
modulePaths.set(moduleName, []);
|
|
148
161
|
}
|
|
149
162
|
const currentModule = modules.get(moduleName);
|
|
@@ -159,6 +172,8 @@ function parseSpecToModules(spec) {
|
|
|
159
172
|
const cleanType = t.replace("[]", "");
|
|
160
173
|
if (cleanType && !["unknown", "undefined", "void", "any", "QueryOptions", "Promise"].includes(cleanType)) {
|
|
161
174
|
currentModule.schemas.add(cleanType);
|
|
175
|
+
const schemaDef = allSchemas.get(cleanType);
|
|
176
|
+
if (schemaDef) Object.keys(schemaDef.enums).forEach((propName) => currentModule.enums.add(`${toPascalCase(cleanType)}${toPascalCase(propName)}Enum`));
|
|
162
177
|
}
|
|
163
178
|
});
|
|
164
179
|
const actionName = getActionName(endpoint.operationId);
|
|
@@ -166,28 +181,28 @@ function parseSpecToModules(spec) {
|
|
|
166
181
|
}
|
|
167
182
|
}
|
|
168
183
|
modules.forEach((mod, name) => {
|
|
169
|
-
|
|
170
|
-
mod.
|
|
171
|
-
Object.values(mod.actions).forEach((action) => {
|
|
172
|
-
const relativePath = action.path.replace(basePath, "").replace(/^\//, "");
|
|
173
|
-
action.path = relativePath === "" ? "/" : relativePath;
|
|
174
|
-
});
|
|
184
|
+
mod.baseEndpoint = findCommonPath(modulePaths.get(name));
|
|
185
|
+
Object.values(mod.actions).forEach((action) => action.path = action.path.replace(mod.baseEndpoint, "").replace(/^\//, "") || "/");
|
|
175
186
|
});
|
|
176
187
|
debugLog("Final Parsed Modules", Object.fromEntries(modules));
|
|
177
|
-
return { modules, allSchemas };
|
|
188
|
+
return { modules, allSchemas, allEnums };
|
|
178
189
|
}
|
|
179
|
-
async function generateModuleFiles(module2, allSchemas, outputDir) {
|
|
190
|
+
async function generateModuleFiles(module2, allSchemas, allEnums, outputDir) {
|
|
180
191
|
const moduleOutputPath = import_path.default.join(outputDir, module2.moduleName);
|
|
181
192
|
if (!import_fs.default.existsSync(moduleOutputPath)) import_fs.default.mkdirSync(moduleOutputPath, { recursive: true });
|
|
182
193
|
console.log(import_chalk.default.cyan(`
|
|
183
194
|
Generating module: ${import_chalk.default.bold(module2.moduleName)}`));
|
|
184
|
-
const
|
|
195
|
+
const schemasToImport = [...module2.schemas].sort();
|
|
196
|
+
const enumsToImport = [...module2.enums].sort();
|
|
197
|
+
const indexContent = [`// This file is auto-generated.
|
|
198
|
+
|
|
199
|
+
export * from './config';`];
|
|
185
200
|
let configContent = `/* eslint-disable */
|
|
186
201
|
// This file is auto-generated.
|
|
187
202
|
|
|
188
203
|
import type { ApiModuleConfig, ActionConfigModule, QueryOptions } from 'api-core-lib';
|
|
189
204
|
`;
|
|
190
|
-
if (
|
|
205
|
+
if (schemasToImport.length > 0) configContent += `import type { ${schemasToImport.join(", ")} } from './types';
|
|
191
206
|
`;
|
|
192
207
|
const actionsType = Object.values(module2.actions).map((a) => ` ${a.name}: ActionConfigModule<${a.inputType}, ${a.outputType}>;`).join("\n");
|
|
193
208
|
const actionsValue = Object.values(module2.actions).map((a) => {
|
|
@@ -206,14 +221,32 @@ ${actionsValue}
|
|
|
206
221
|
`;
|
|
207
222
|
import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "config.ts"), configContent.trim());
|
|
208
223
|
console.log(import_chalk.default.gray(` \u2713 config.ts`));
|
|
209
|
-
|
|
224
|
+
if (schemasToImport.length > 0) {
|
|
225
|
+
if (enumsToImport.length > 0) {
|
|
226
|
+
let enumsContent = `// This file is auto-generated.
|
|
210
227
|
|
|
211
|
-
|
|
212
|
-
|
|
228
|
+
`;
|
|
229
|
+
for (const enumName of enumsToImport) {
|
|
230
|
+
const enumDef = allEnums.get(enumName);
|
|
231
|
+
if (enumDef) {
|
|
232
|
+
enumsContent += `export const ${enumName} = ${JSON.stringify(enumDef.values)} as const;
|
|
233
|
+
`;
|
|
234
|
+
enumsContent += `export type ${enumName} = typeof ${enumName}[number];
|
|
235
|
+
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
import_fs.default.writeFileSync(import_path.default.join(moduleOutputPath, "enums.ts"), enumsContent);
|
|
240
|
+
console.log(import_chalk.default.gray(` \u2713 enums.ts`));
|
|
241
|
+
indexContent.push(`export * from './enums';`);
|
|
242
|
+
}
|
|
213
243
|
let typesContent = `// This file is auto-generated.
|
|
214
244
|
|
|
215
245
|
`;
|
|
216
|
-
|
|
246
|
+
if (enumsToImport.length > 0) typesContent += `import type { ${enumsToImport.join(", ")} } from './enums';
|
|
247
|
+
|
|
248
|
+
`;
|
|
249
|
+
for (const typeName of schemasToImport) {
|
|
217
250
|
const parsedSchema = allSchemas.get(typeName);
|
|
218
251
|
if (parsedSchema) {
|
|
219
252
|
if (parsedSchema.description) typesContent += `/**
|
|
@@ -228,7 +261,10 @@ export * from './config';`];
|
|
|
228
261
|
${prop.example ? ` * @example ${JSON.stringify(prop.example)}
|
|
229
262
|
` : ""} */
|
|
230
263
|
`;
|
|
231
|
-
|
|
264
|
+
let propType = prop.type;
|
|
265
|
+
if (prop.enum) propType = `${toPascalCase(typeName)}${toPascalCase(prop.name)}Enum`;
|
|
266
|
+
else if (prop.items) propType = prop.items.name ? `${toPascalCase(prop.items.name)}[]` : `${prop.items.type || "unknown"}[]`;
|
|
267
|
+
else if (prop.type === "object") propType = prop.properties && prop.properties.length > 0 ? `Record<string, unknown>` : `Record<string, unknown>`;
|
|
232
268
|
typesContent += ` ${prop.name}${prop.isRequired ? "" : "?"}: ${propType};
|
|
233
269
|
`;
|
|
234
270
|
}
|
|
@@ -242,9 +278,11 @@ ${prop.example ? ` * @example ${JSON.stringify(prop.example)}
|
|
|
242
278
|
indexContent.push(`export * from './types';`);
|
|
243
279
|
let validationContent = `// This file is auto-generated.
|
|
244
280
|
import { z } from 'zod';
|
|
281
|
+
`;
|
|
282
|
+
if (enumsToImport.length > 0) validationContent += `import { ${enumsToImport.join(", ")} } from './enums';
|
|
245
283
|
|
|
246
284
|
`;
|
|
247
|
-
for (const typeName of
|
|
285
|
+
for (const typeName of schemasToImport) {
|
|
248
286
|
const parsedSchema = allSchemas.get(typeName);
|
|
249
287
|
if (parsedSchema) {
|
|
250
288
|
let zodShape = parsedSchema.properties.map((p) => ` ${p.name}: ${_propToZod(p)}`).join(",\n");
|
|
@@ -259,10 +297,12 @@ ${zodShape}
|
|
|
259
297
|
console.log(import_chalk.default.gray(` \u2713 validation.ts`));
|
|
260
298
|
indexContent.push(`export * from './validation';`);
|
|
261
299
|
let mocksContent = `// This file is auto-generated.
|
|
262
|
-
import type { ${
|
|
300
|
+
import type { ${schemasToImport.join(", ")} } from './types';
|
|
301
|
+
`;
|
|
302
|
+
if (enumsToImport.length > 0) mocksContent += `import { ${enumsToImport.join(", ")} } from './enums';
|
|
263
303
|
|
|
264
304
|
`;
|
|
265
|
-
for (const typeName of
|
|
305
|
+
for (const typeName of schemasToImport) {
|
|
266
306
|
const parsedSchema = allSchemas.get(typeName);
|
|
267
307
|
if (parsedSchema) {
|
|
268
308
|
let mockObject = {};
|
|
@@ -346,79 +386,34 @@ ${shape}
|
|
|
346
386
|
}
|
|
347
387
|
return chain;
|
|
348
388
|
}
|
|
349
|
-
function _generateUUID() {
|
|
350
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
|
351
|
-
const r = Math.random() * 16 | 0;
|
|
352
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
353
|
-
return v.toString(16);
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
function _getRandomInt(min, max) {
|
|
357
|
-
min = Math.ceil(min);
|
|
358
|
-
max = Math.floor(max);
|
|
359
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
360
|
-
}
|
|
361
|
-
var _firstNames = ["Ahmed", "Fatima", "Mohammed", "Zainab", "Ali", "Nour"];
|
|
362
|
-
var _lastNames = ["Al-Masri", "Khan", "Hassan", "Abbas", "Said"];
|
|
363
389
|
function _propToMock(prop) {
|
|
364
|
-
if (prop.example
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (prop.enum && prop.enum.length > 0) {
|
|
368
|
-
return prop.enum[_getRandomInt(0, prop.enum.length - 1)];
|
|
369
|
-
}
|
|
390
|
+
if (prop.example) return prop.example;
|
|
391
|
+
if (prop.name.match(/image|avatar|logo|url/i)) return "https://via.placeholder.com/150";
|
|
392
|
+
if (prop.enum) return prop.enum[0];
|
|
370
393
|
switch (prop.type) {
|
|
371
394
|
case "string":
|
|
372
|
-
if (prop.format === "email") return
|
|
373
|
-
if (prop.format === "uuid") return
|
|
374
|
-
|
|
375
|
-
if (prop.format === "datetime") return (/* @__PURE__ */ new Date()).toISOString();
|
|
376
|
-
const name = prop.name.toLowerCase();
|
|
377
|
-
if (name.includes("image") || name.includes("avatar") || name.includes("logo") || name.includes("picture")) return `https://via.placeholder.com/${_getRandomInt(150, 400)}`;
|
|
378
|
-
if (name.includes("firstname")) return _firstNames[_getRandomInt(0, _firstNames.length - 1)];
|
|
379
|
-
if (name.includes("lastname")) return _lastNames[_getRandomInt(0, _lastNames.length - 1)];
|
|
380
|
-
if (name.includes("name")) return `${_firstNames[_getRandomInt(0, _firstNames.length - 1)]} ${_lastNames[_getRandomInt(0, _lastNames.length - 1)]}`;
|
|
381
|
-
if (name.includes("city")) return "Riyadh";
|
|
382
|
-
if (name.includes("country")) return "Saudi Arabia";
|
|
383
|
-
if (name.includes("phone")) return `+1-${_getRandomInt(100, 999)}-${_getRandomInt(100, 999)}-${_getRandomInt(1e3, 9999)}`;
|
|
384
|
-
if (name.includes("description") || name.includes("comment")) return "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
|
385
|
-
const minLen = prop.minLength || 8;
|
|
386
|
-
const maxLen = prop.maxLength || 16;
|
|
387
|
-
return `Mock${toPascalCase(prop.name)}`.padEnd(_getRandomInt(minLen, maxLen), "x");
|
|
395
|
+
if (prop.format === "email") return "test@example.com";
|
|
396
|
+
if (prop.format === "uuid") return "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11";
|
|
397
|
+
return `Mock ${toPascalCase(prop.name)}`;
|
|
388
398
|
case "integer":
|
|
389
399
|
case "number":
|
|
390
|
-
|
|
391
|
-
const max = prop.maximum === void 0 ? 1e3 : prop.maximum;
|
|
392
|
-
const randomNum = _getRandomInt(min, max);
|
|
393
|
-
return prop.type === "integer" ? Math.floor(randomNum) : randomNum + Math.random();
|
|
400
|
+
return 1;
|
|
394
401
|
case "boolean":
|
|
395
|
-
return
|
|
402
|
+
return true;
|
|
396
403
|
case "array":
|
|
397
|
-
|
|
398
|
-
const maxItems = prop.maxItems || 3;
|
|
399
|
-
const count = _getRandomInt(minItems, maxItems);
|
|
400
|
-
if (!prop.items) return [];
|
|
401
|
-
const items = [];
|
|
402
|
-
for (let i = 0; i < count; i++) {
|
|
403
|
-
items.push(_propToMock(prop.items));
|
|
404
|
-
}
|
|
405
|
-
return items;
|
|
404
|
+
return prop.items ? [_propToMock(prop.items)] : [];
|
|
406
405
|
case "object":
|
|
407
406
|
const mock = {};
|
|
408
|
-
if (prop.properties) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
mock[p.name] = _propToMock(p);
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
}
|
|
407
|
+
if (prop.properties) prop.properties.forEach((p) => {
|
|
408
|
+
mock[p.name] = _propToMock(p);
|
|
409
|
+
});
|
|
415
410
|
return mock;
|
|
416
411
|
default:
|
|
417
412
|
return null;
|
|
418
413
|
}
|
|
419
414
|
}
|
|
420
415
|
async function runGenerator(options) {
|
|
421
|
-
console.log(import_chalk.default.cyan.bold("\u{1F680} Starting API Development Platform Generator (
|
|
416
|
+
console.log(import_chalk.default.cyan.bold("\u{1F680} Starting API Development Platform Generator (Sapphire Edition)..."));
|
|
422
417
|
import_dotenv.default.config({ path: options.envPath });
|
|
423
418
|
const specUrl = process.env.OPENAPI_SPEC_URL || "./swagger.json";
|
|
424
419
|
try {
|
|
@@ -426,13 +421,12 @@ async function runGenerator(options) {
|
|
|
426
421
|
\u23F3 Step 1: Dereferencing spec from ${specUrl}...`));
|
|
427
422
|
const spec = await import_swagger_parser.default.dereference(specUrl);
|
|
428
423
|
console.log(import_chalk.default.green("\u2713 Spec fully dereferenced."));
|
|
429
|
-
debugLog("Dereferenced Spec", spec);
|
|
430
424
|
console.log(import_chalk.default.blue("\n\u23F3 Step 2: Parsing spec with intelligent grouping..."));
|
|
431
|
-
const { modules, allSchemas } = parseSpecToModules(spec);
|
|
425
|
+
const { modules, allSchemas, allEnums } = parseSpecToModules(spec);
|
|
432
426
|
console.log(import_chalk.default.green(`\u2713 Found and grouped ${modules.size} logical modules.`));
|
|
433
427
|
console.log(import_chalk.default.blue("\n\u23F3 Step 3: Generating all module files..."));
|
|
434
428
|
for (const module2 of modules.values()) {
|
|
435
|
-
await generateModuleFiles(module2, allSchemas, options.output);
|
|
429
|
+
await generateModuleFiles(module2, allSchemas, allEnums, options.output);
|
|
436
430
|
}
|
|
437
431
|
console.log(import_chalk.default.green("\n\u2713 All module files generated successfully."));
|
|
438
432
|
console.log(import_chalk.default.bold.green("\n\u{1F389} API generation complete! Your development platform is ready."));
|