api-core-lib 12.0.64 → 12.0.66

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +109 -202
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -74,112 +74,36 @@ var import_dotenv = __toESM(require("dotenv"), 1);
74
74
  var import_swagger_parser = __toESM(require("@apidevtools/swagger-parser"), 1);
75
75
  var import_openapi_types = __toESM(require_dist(), 1);
76
76
  var import_json_schema_to_typescript = require("json-schema-to-typescript");
77
- function preprocessSpec(spec) {
78
- if (!spec.components) spec.components = {};
79
- if (!spec.components.schemas) spec.components.schemas = {};
80
- const schemas = spec.components.schemas;
81
- for (const apiPath in spec.paths) {
82
- const pathItem = spec.paths[apiPath];
83
- if (!pathItem) continue;
84
- for (const method in pathItem) {
85
- if (!Object.values(import_openapi_types.OpenAPIV3.HttpMethods).includes(method)) continue;
86
- const endpoint = pathItem[method];
87
- const baseName = _generateBaseNameForSchema(method, apiPath, endpoint.operationId);
88
- const requestBody = endpoint.requestBody;
89
- if (requestBody?.content?.["application/json"]?.schema) {
90
- requestBody.content["application/json"].schema = _normalizeSchema(
91
- requestBody.content["application/json"].schema,
92
- schemas,
93
- baseName,
94
- "Request"
95
- );
96
- }
97
- if (endpoint.responses) {
98
- for (const statusCode in endpoint.responses) {
99
- const response = endpoint.responses[statusCode];
100
- const mediaTypeObject = response.content?.["application/json"];
101
- if (mediaTypeObject?.schema) {
102
- mediaTypeObject.schema = _normalizeSchema(
103
- mediaTypeObject.schema,
104
- // مرر المخطط الأصلي
105
- schemas,
106
- baseName,
107
- `Response${statusCode}`
108
- );
109
- }
110
- }
111
- }
112
- }
113
- }
114
- return spec;
115
- }
116
- function _normalizeSchema(schema, schemas, baseName, suffix) {
117
- if (!schema || "$ref" in schema) {
118
- return schema;
119
- }
120
- if (schema.type === "array" && schema.items) {
121
- schema.items = _normalizeSchema(schema.items, schemas, baseName, suffix.replace("[]", "Item"));
122
- return schema;
123
- }
124
- if (schema.type === "object" && schema.properties) {
125
- const typeName = `${_toPascalCase(baseName)}${_toPascalCase(suffix)}Dto`;
126
- if (!schemas[typeName]) {
127
- schemas[typeName] = schema;
128
- for (const propName in schema.properties) {
129
- schema.properties[propName] = _normalizeSchema(
130
- schema.properties[propName],
131
- schemas,
132
- baseName,
133
- `${_toPascalCase(propName)}Property`
134
- );
135
- }
136
- }
137
- return { $ref: `#/components/schemas/${typeName}` };
138
- }
139
- return schema;
140
- }
141
- function _generateBaseNameForSchema(method, path3, operationId) {
142
- if (operationId) return _toPascalCase(operationId.replace(/_v\d+$/, ""));
143
- const pathPart = path3.replace(/[\/{}]/g, " ").trim();
144
- return `${_toPascalCase(method)} ${_toPascalCase(pathPart)}`;
145
- }
146
77
  function parseSpecToModules(spec) {
147
78
  const modules = {};
79
+ const modulePaths = {};
80
+ if (!spec.components) spec.components = {};
81
+ if (!spec.components.schemas) spec.components.schemas = {};
82
+ const allSchemas = spec.components.schemas;
148
83
  for (const apiPath in spec.paths) {
149
84
  const pathItem = spec.paths[apiPath];
150
85
  if (!pathItem) continue;
151
86
  for (const method in pathItem) {
152
87
  if (!Object.values(import_openapi_types.OpenAPIV3.HttpMethods).includes(method)) continue;
153
88
  const endpoint = pathItem[method];
154
- if (!endpoint.tags || endpoint.tags.length === 0) continue;
155
- const tagName = endpoint.tags[0];
156
- const moduleName = _sanitizeTagName(tagName) + "Api";
89
+ if (!endpoint.operationId) continue;
90
+ const moduleName = _getModuleNameFromOperationId(endpoint.operationId);
157
91
  if (!modules[moduleName]) {
158
- modules[moduleName] = { baseEndpoint: _findCommonBasePath(spec, tagName), actions: {}, types: /* @__PURE__ */ new Set() };
159
- }
160
- const currentModule = modules[moduleName];
161
- const successResponseKey = Object.keys(endpoint.responses).find((code) => code.startsWith("2"));
162
- const successResponse = successResponseKey ? endpoint.responses[successResponseKey] : void 0;
163
- const responseSchema = successResponse?.content?.["application/json"]?.schema;
164
- const outputType = successResponseKey === "204" ? "void" : _extractTypeNameFromSchema(responseSchema);
165
- const requestBody = endpoint.requestBody;
166
- const requestSchema = requestBody?.content?.["application/json"]?.schema;
167
- let inputType = "undefined";
168
- if (requestSchema) {
169
- inputType = _extractTypeNameFromSchema(requestSchema);
170
- } else if ((endpoint.parameters || []).some((p) => p.in === "query")) {
171
- inputType = "QueryOptions";
92
+ modules[moduleName] = { baseEndpoint: "", actions: {}, types: /* @__PURE__ */ new Set() };
93
+ modulePaths[moduleName] = [];
172
94
  }
95
+ modulePaths[moduleName].push(apiPath);
96
+ const { inputType, outputType } = _extractInputOutputTypes(endpoint, allSchemas);
173
97
  [inputType, outputType].forEach((t) => {
174
98
  if (t && !["unknown", "undefined", "void", "any", "QueryOptions", "Promise"].includes(t)) {
175
- currentModule.types.add(t.replace("[]", ""));
99
+ modules[moduleName].types.add(t.replace("[]", ""));
176
100
  }
177
101
  });
178
- const actionName = _sanitizeActionName(endpoint.operationId, method, apiPath);
179
- const relativePath = apiPath.replace(currentModule.baseEndpoint, "").replace(/^\//, "");
180
- currentModule.actions[actionName] = {
102
+ const actionName = _sanitizeActionName(endpoint.operationId);
103
+ modules[moduleName].actions[actionName] = {
181
104
  method: method.toUpperCase(),
182
- path: relativePath === "" ? "/" : relativePath,
105
+ path: apiPath,
106
+ // سنقوم بتعديل هذا لاحقًا ليصبح نسبيًا
183
107
  description: endpoint.summary || "",
184
108
  hasQuery: (endpoint.parameters || []).some((p) => p.in === "query"),
185
109
  autoFetch: method.toUpperCase() === "GET" && !apiPath.includes("{"),
@@ -189,74 +113,90 @@ function parseSpecToModules(spec) {
189
113
  };
190
114
  }
191
115
  }
116
+ for (const moduleName in modules) {
117
+ const basePath = _findCommonBasePath(modulePaths[moduleName]);
118
+ modules[moduleName].baseEndpoint = basePath;
119
+ for (const actionName in modules[moduleName].actions) {
120
+ const action = modules[moduleName].actions[actionName];
121
+ const relativePath = action.path.replace(basePath, "").replace(/^\//, "");
122
+ action.path = relativePath === "" ? "/" : relativePath;
123
+ }
124
+ }
192
125
  return modules;
193
126
  }
194
- function _extractTypeNameFromSchema(schema) {
127
+ function _extractInputOutputTypes(endpoint, schemas) {
128
+ const operationId = endpoint.operationId || "UnnamedOperation";
129
+ const successResponseKey = Object.keys(endpoint.responses).find((code) => code.startsWith("2"));
130
+ const successResponse = successResponseKey ? endpoint.responses[successResponseKey] : void 0;
131
+ const responseSchema = successResponse?.content?.["application/json"]?.schema;
132
+ const outputType = successResponseKey === "204" ? "void" : _schemaToTypeName(responseSchema, `${operationId}Response`, schemas);
133
+ const requestBody = endpoint.requestBody;
134
+ const requestSchema = requestBody?.content?.["application/json"]?.schema;
135
+ let inputType = "undefined";
136
+ if (requestSchema) {
137
+ inputType = _schemaToTypeName(requestSchema, `${operationId}Request`, schemas);
138
+ } else if ((endpoint.parameters || []).some((p) => p.in === "query")) {
139
+ inputType = "QueryOptions";
140
+ }
141
+ return { inputType, outputType };
142
+ }
143
+ function _schemaToTypeName(schema, name, schemas) {
195
144
  if (!schema) return "unknown";
196
- if ("$ref" in schema && schema.$ref) return _refToTypeName(schema.$ref);
197
- if (!("$ref" in schema)) {
198
- if (schema.type === "array" && schema.items) {
199
- const itemType = _extractTypeNameFromSchema(schema.items);
200
- return itemType !== "unknown" ? `${itemType}[]` : "unknown[]";
145
+ if (schema.type === "array" && schema.items) {
146
+ const itemTypeName = _schemaToTypeName(schema.items, `${name}Item`, schemas);
147
+ return `${itemTypeName}[]`;
148
+ }
149
+ if (schema.type === "object" || schema.properties || schema.allOf) {
150
+ const typeName = _toPascalCase(name);
151
+ if (!schemas[typeName]) {
152
+ schemas[typeName] = schema;
201
153
  }
202
- if (schema.type && ["string", "number", "boolean", "integer"].includes(schema.type)) {
203
- return schema.type === "integer" ? "number" : schema.type;
154
+ return typeName;
155
+ }
156
+ if (schema.type && ["string", "number", "boolean", "integer"].includes(schema.type)) {
157
+ return schema.type === "integer" ? "number" : schema.type;
158
+ }
159
+ if (Object.keys(schema).length === 0) {
160
+ const typeName = _toPascalCase(name);
161
+ if (!schemas[typeName]) {
162
+ schemas[typeName] = { type: "object", properties: {}, description: "Represents a dynamic or empty object." };
204
163
  }
164
+ return typeName;
205
165
  }
206
166
  return "unknown";
207
167
  }
208
- async function generateModuleFiles(moduleName, moduleData, spec, outputDir) {
168
+ async function generateModuleFiles(moduleName, moduleData, allSchemas, outputDir) {
209
169
  console.log(import_chalk.default.gray(` Generating files for module: ${import_chalk.default.bold(moduleName)}...`));
210
170
  const moduleFolderPath = import_path.default.join(outputDir, moduleName);
211
- if (!import_fs.default.existsSync(moduleFolderPath)) {
212
- import_fs.default.mkdirSync(moduleFolderPath, { recursive: true });
213
- }
214
- await _generateTypesFile(moduleFolderPath, moduleName, moduleData.types, spec);
171
+ if (!import_fs.default.existsSync(moduleFolderPath)) import_fs.default.mkdirSync(moduleFolderPath, { recursive: true });
172
+ await _generateTypesFile(moduleFolderPath, moduleName, moduleData.types, allSchemas);
215
173
  await _generateConfigFile(moduleFolderPath, moduleName, moduleData);
216
174
  }
217
- async function _generateTypesFile(moduleFolderPath, moduleName, typeNames, spec) {
175
+ async function _generateTypesFile(moduleFolderPath, moduleName, typeNames, allSchemas) {
218
176
  if (typeNames.size === 0) {
219
- console.log(import_chalk.default.yellow(` - No types found for module "${moduleName}". Skipping types.ts generation.`));
220
- const typesFilePath2 = import_path.default.join(moduleFolderPath, "types.ts");
221
- import_fs.default.writeFileSync(typesFilePath2, `// No types found for this module.
222
- `);
177
+ console.log(import_chalk.default.yellow(` - No types for module "${moduleName}". Skipping types.ts.`));
223
178
  return;
224
179
  }
225
180
  console.log(import_chalk.default.gray(` - Generating types.ts with ${typeNames.size} types...`));
226
181
  let typesContent = `// This file is auto-generated by the API generator. Do not edit.
227
182
 
228
183
  `;
229
- const allSchemas = spec.components?.schemas || {};
230
184
  const compilationOptions = {
231
185
  bannerComment: "",
232
186
  style: { bracketSpacing: true, printWidth: 120, semi: true, singleQuote: true, tabWidth: 2, trailingComma: "es5", useTabs: false },
233
- additionalProperties: false,
234
- declareExternallyReferenced: true
235
- };
236
- const schemaContainer = {
237
- components: {
238
- schemas: allSchemas
239
- }
187
+ additionalProperties: false
240
188
  };
241
189
  for (const typeName of Array.from(typeNames).sort()) {
242
- if (allSchemas[typeName]) {
190
+ const schema = allSchemas[typeName];
191
+ if (schema) {
243
192
  try {
244
- const tempSchemaToCompile = {
245
- $ref: `#/components/schemas/${typeName}`
246
- };
247
- const finalSchema = { ...schemaContainer, ...tempSchemaToCompile };
248
- const tsType = await (0, import_json_schema_to_typescript.compile)(
249
- finalSchema,
250
- typeName,
251
- // <-- هذا الاسم سيُستخدم الآن بنجاح!
252
- compilationOptions
253
- );
193
+ const tsType = await (0, import_json_schema_to_typescript.compile)(schema, typeName, compilationOptions);
254
194
  typesContent += tsType + "\n";
255
195
  } catch (compileError) {
256
196
  console.error(import_chalk.default.red(` - Error compiling type "${typeName}": ${compileError.message}`));
257
197
  }
258
198
  } else {
259
- console.log(import_chalk.default.yellow(` - Warning: Schema for type "${typeName}" not found, skipping.`));
199
+ console.log(import_chalk.default.yellow(` - Warning: Schema for type "${typeName}" not found.`));
260
200
  }
261
201
  }
262
202
  const typesFilePath = import_path.default.join(moduleFolderPath, "types.ts");
@@ -266,26 +206,24 @@ async function _generateConfigFile(moduleFolderPath, moduleName, moduleData) {
266
206
  const actionsCount = Object.keys(moduleData.actions).length;
267
207
  console.log(import_chalk.default.gray(` - Generating config.ts with ${actionsCount} actions...`));
268
208
  const typeNamesArray = [...moduleData.types].sort();
269
- const typesImportStatement = typeNamesArray.length > 0 ? `import type { ${typeNamesArray.join(", ")} } from './types';` : `// No types to import for this module`;
209
+ const typesImportStatement = typeNamesArray.length > 0 ? `import type { ${typeNamesArray.join(", ")} } from './types';` : ``;
270
210
  const actionsTypeParts = Object.entries(moduleData.actions).map(
271
- ([actionName, actionData]) => ` ${actionName}: ActionConfigModule<${actionData._inputType || "undefined"}, ${actionData._outputType || "unknown"}>;`
211
+ ([actionName, actionData]) => ` ${actionName}: ActionConfigModule<${actionData._inputType}, ${actionData._outputType}>;`
272
212
  );
273
213
  const actionsTypeDefinition = `{
274
214
  ${actionsTypeParts.join("\n")}
275
215
  }`;
276
- const actionsValueParts = Object.entries(moduleData.actions).map(
277
- ([actionName, actionData]) => {
278
- const { _inputType, _outputType, ...config } = actionData;
279
- return ` ${actionName}: ${JSON.stringify(config, null, 2).replace(/\n/g, "\n ")}`;
280
- }
281
- );
216
+ const actionsValueParts = Object.entries(moduleData.actions).map(([actionName, actionData]) => {
217
+ const { _inputType, _outputType, ...config } = actionData;
218
+ return ` ${actionName}: ${JSON.stringify(config, null, 2).replace(/\n/g, "\n ")}`;
219
+ });
282
220
  const actionsValueDefinition = `{
283
221
  ${actionsValueParts.join(",\n")}
284
222
  }`;
285
223
  const configContent = `/* eslint-disable */
286
- // This file is auto-generated by the API generator. Do not edit.
224
+ // This file is auto-generated...
287
225
 
288
- import type { ApiModuleConfig, ActionConfigModule, QueryOptions } from 'api-core-lib'; // \u0627\u0641\u062A\u0631\u0636 \u0623\u0646 \u0647\u0630\u0627 \u0627\u0644\u0645\u0633\u0627\u0631 \u0635\u062D\u064A\u062D
226
+ import type { ApiModuleConfig, ActionConfigModule, QueryOptions } from 'api-core-lib';
289
227
  ${typesImportStatement}
290
228
 
291
229
  export const ${moduleName}Module: ApiModuleConfig<${actionsTypeDefinition}> = {
@@ -297,91 +235,60 @@ export const ${moduleName}Module: ApiModuleConfig<${actionsTypeDefinition}> = {
297
235
  import_fs.default.writeFileSync(configFilePath, configContent.trim(), { encoding: "utf-8" });
298
236
  }
299
237
  function _toPascalCase(str) {
300
- return str.replace(/[^a-zA-Z0-9]/g, " ").replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase()).replace(/\s+/g, "");
238
+ return str.replace(/[^a-zA-Z0-9]/g, " ").replace(/(?:^\w|[A-Z]|\b\w)/g, (w) => w.toUpperCase()).replace(/\s+/g, "");
301
239
  }
302
- function _refToTypeName(ref) {
303
- if (!ref) return "unknown";
304
- return ref.split("/").pop() || "unknown";
240
+ function _getModuleNameFromOperationId(operationId) {
241
+ const controllerName = operationId.split("_")[0].replace(/Controller$/, "");
242
+ return `${controllerName}Api`;
305
243
  }
306
- function _sanitizeTagName(tagName) {
307
- return tagName.replace(/[^a-zA-Z0-9]/g, "").replace(/Central|Tenant/g, "");
308
- }
309
- function _sanitizeActionName(operationId, method, path3) {
310
- if (operationId) {
311
- const name = operationId.split("_").slice(1).join("_");
312
- const sanitized = name.charAt(0).toLowerCase() + name.slice(1).replace(/_v\d+$/, "");
313
- return sanitized || "action";
314
- }
315
- const pathPart = path3.replace(/[\/{}]/g, "_").replace(/^_|_$/g, "");
316
- return `${method.toLowerCase()}_${pathPart}`;
244
+ function _sanitizeActionName(operationId) {
245
+ const nameParts = operationId.split("_");
246
+ if (nameParts.length <= 1) return operationId;
247
+ const actionPart = nameParts[1];
248
+ return actionPart.charAt(0).toLowerCase() + actionPart.slice(1);
317
249
  }
318
- function _findCommonBasePath(spec, tagName) {
319
- const paths = Object.keys(spec.paths).filter((p) => {
320
- const pathItem = spec.paths[p];
321
- if (!pathItem) return false;
322
- for (const method in pathItem) {
323
- const endpoint = pathItem[method];
324
- if (endpoint.tags?.includes(tagName)) return true;
325
- }
326
- return false;
327
- });
328
- if (paths.length === 0) return "/";
329
- let commonPrefix = paths[0];
330
- for (let i = 1; i < paths.length; i++) {
331
- while (paths[i].indexOf(commonPrefix) !== 0) {
332
- commonPrefix = commonPrefix.substring(0, commonPrefix.length - 1);
333
- }
250
+ function _findCommonBasePath(paths) {
251
+ if (!paths || paths.length === 0) return "/";
252
+ const sortedPaths = [...paths].sort();
253
+ const first = sortedPaths[0];
254
+ const last = sortedPaths[sortedPaths.length - 1];
255
+ let i = 0;
256
+ while (i < first.length && first.charAt(i) === last.charAt(i)) {
257
+ i++;
334
258
  }
335
- return commonPrefix.substring(0, commonPrefix.lastIndexOf("/") + 1) || "/";
259
+ let prefix = first.substring(0, i);
260
+ return prefix.substring(0, prefix.lastIndexOf("/") + 1) || "/";
336
261
  }
337
262
  async function runGenerator(options) {
338
263
  console.log(import_chalk.default.cyan.bold("\u{1F680} Starting API Core Lib Code Generator..."));
339
264
  import_dotenv.default.config({ path: options.envPath });
340
- const specUrl = getSpecUrl();
265
+ const specUrl = process.env.OPENAPI_SPEC_URL || "./swagger.json";
341
266
  console.log(import_chalk.default.blue("\u2713 Step 1: Environment variables loaded."));
342
267
  try {
343
268
  console.log(import_chalk.default.blue(`
344
- \u23F3 Step 2: Fetching, validating, and bundling spec from ${specUrl}...`));
345
- const bundledSpec = await import_swagger_parser.default.bundle(specUrl);
346
- console.log(import_chalk.default.green("\u2713 Spec fetched and fully bundled successfully."));
347
- console.log(import_chalk.default.blue("\n\u23F3 Step 3: Preprocessing spec to normalize any remaining inline schemas..."));
348
- const normalizedSpec = preprocessSpec(bundledSpec);
349
- console.log(import_chalk.default.green("\u2713 Spec has been normalized successfully."));
350
- console.log(import_chalk.default.blue("\n\u23F3 Step 4: Parsing spec and generating API modules structure..."));
351
- const modules = parseSpecToModules(normalizedSpec);
269
+ \u23F3 Step 2: Fetching and fully dereferencing spec from ${specUrl}...`));
270
+ const dereferencedSpec = await import_swagger_parser.default.dereference(specUrl);
271
+ console.log(import_chalk.default.green("\u2713 Spec fetched and fully dereferenced successfully."));
272
+ console.log(import_chalk.default.blue("\n\u23F3 Step 3: Parsing spec using intelligent grouping..."));
273
+ const modules = parseSpecToModules(dereferencedSpec);
352
274
  const modulesCount = Object.keys(modules).length;
353
- console.log(import_chalk.default.green(`\u2713 Found ${modulesCount} modules to generate.`));
354
- if (modulesCount === 0) {
355
- console.log(import_chalk.default.yellow("Warning: No modules were found in the spec."));
356
- }
357
- console.log(import_chalk.default.blue("\n\u23F3 Step 5: Generating module files..."));
275
+ console.log(import_chalk.default.green(`\u2713 Found and grouped ${modulesCount} logical modules.`));
276
+ console.log(import_chalk.default.blue("\n\u23F3 Step 4: Generating module files..."));
358
277
  const modulesOutputPath = import_path.default.join(options.output, "modules");
278
+ const allSchemas = dereferencedSpec.components?.schemas || {};
359
279
  for (const moduleName in modules) {
360
280
  const moduleData = modules[moduleName];
361
- await generateModuleFiles(moduleName, moduleData, normalizedSpec, modulesOutputPath);
281
+ await generateModuleFiles(moduleName, moduleData, allSchemas, modulesOutputPath);
362
282
  }
363
283
  console.log(import_chalk.default.green("\u2713 All module files generated."));
364
284
  console.log(import_chalk.default.bold.green("\n\u{1F389} API generation complete!"));
365
285
  console.log(import_chalk.default.bold.cyan(` Output directory: ${options.output}`));
366
286
  } catch (error) {
367
- handleGenerationError(error);
368
- }
369
- }
370
- function getSpecUrl() {
371
- const specUrl = process.env.OPENAPI_SPEC_URL || (process.env.API_URL ? `${process.env.API_URL}/docs-json` : null) || (process.env.NEXT_PUBLIC_API_URL ? `${process.env.NEXT_PUBLIC_API_URL}/docs-json` : null);
372
- if (!specUrl) {
373
- console.error(import_chalk.default.red.bold("\n\u274C Error: API specification URL not found."));
287
+ console.error(import_chalk.default.red.bold("\n\u274C An error occurred during generation:"));
288
+ console.error(import_chalk.default.red(`Error Message: ${error.message}`));
289
+ if (error.stack) console.error(import_chalk.default.gray(error.stack));
374
290
  process.exit(1);
375
291
  }
376
- return specUrl;
377
- }
378
- function handleGenerationError(error) {
379
- console.error(import_chalk.default.red.bold("\n\u274C An error occurred during generation:"));
380
- console.error(import_chalk.default.red(`Error Message: ${error.message}`));
381
- if (error.stack) {
382
- console.error(import_chalk.default.gray(error.stack));
383
- }
384
- process.exit(1);
385
292
  }
386
293
 
387
294
  // src/cli.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-core-lib",
3
- "version": "12.0.64",
3
+ "version": "12.0.66",
4
4
  "description": "A flexible and powerful API client library for modern web applications.",
5
5
  "type": "module",
6
6
  "exports": {