api-core-lib 12.0.26 → 12.0.27
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/{apiModule.types-Bn0tJF7b.d.cts → apiModule.types-Bg7m0Xoy.d.cts} +1 -1
- package/dist/{apiModule.types-Bn0tJF7b.d.ts → apiModule.types-Bg7m0Xoy.d.ts} +1 -1
- package/dist/chunk-NCMDUWNO.cjs +388 -0
- package/dist/chunk-NRV3EMIJ.js +8 -0
- package/dist/chunk-QOOQGLEX.cjs +8 -0
- package/dist/chunk-UWAVU6EF.js +388 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +107 -0
- package/dist/client.cjs +105 -520
- package/dist/client.d.cts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +12 -387
- package/dist/index.cjs +46 -457
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -380
- package/dist/server.cjs +12 -303
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +7 -263
- package/dist/{useApiRecord.types-CWhwCAB6.d.ts → useApiRecord.types-BsemqlQl.d.ts} +1 -1
- package/dist/{useApiRecord.types-NZtPRga3.d.cts → useApiRecord.types-CT4BDrvj.d.cts} +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__publicField
|
|
4
|
+
} from "./chunk-NRV3EMIJ.js";
|
|
5
|
+
|
|
6
|
+
// src/core/utils.ts
|
|
7
|
+
import axios from "axios";
|
|
8
|
+
function isAxiosResponse(obj) {
|
|
9
|
+
return obj && obj.data !== void 0 && obj.status !== void 0 && obj.config !== void 0;
|
|
10
|
+
}
|
|
11
|
+
function buildPaginateQuery(options) {
|
|
12
|
+
const params = new URLSearchParams();
|
|
13
|
+
for (const key in options) {
|
|
14
|
+
const value = options[key];
|
|
15
|
+
if (value === null || value === void 0) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (key === "filter" && typeof value === "object" && !Array.isArray(value)) {
|
|
19
|
+
for (const filterKey in value) {
|
|
20
|
+
const filterValue = value[filterKey];
|
|
21
|
+
if (filterValue !== null && filterValue !== void 0) {
|
|
22
|
+
params.append(`filter[${filterKey}]`, String(filterValue));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} else if (key === "sortBy" && Array.isArray(value)) {
|
|
26
|
+
value.forEach((sortItem) => {
|
|
27
|
+
if (sortItem && sortItem.key && sortItem.direction) {
|
|
28
|
+
params.append("sortBy[]", `${sortItem.key}:${sortItem.direction}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
params.append(key, String(value));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return params.toString();
|
|
36
|
+
}
|
|
37
|
+
function isServerError(error) {
|
|
38
|
+
return axios.isAxiosError(error) && error.response !== void 0;
|
|
39
|
+
}
|
|
40
|
+
function isNetworkError(error) {
|
|
41
|
+
return axios.isAxiosError(error) && error.response === void 0 && error.request !== void 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/core/processor.ts
|
|
45
|
+
import axios2 from "axios";
|
|
46
|
+
var processResponse = (responseOrError) => {
|
|
47
|
+
if (isAxiosResponse(responseOrError)) {
|
|
48
|
+
const response = responseOrError;
|
|
49
|
+
const rawData = response.data;
|
|
50
|
+
const isStandardApiResponse = rawData && typeof rawData.success === "boolean" && rawData.data !== void 0;
|
|
51
|
+
return {
|
|
52
|
+
data: isStandardApiResponse ? rawData.data : rawData,
|
|
53
|
+
links: isStandardApiResponse ? rawData.links : void 0,
|
|
54
|
+
meta: isStandardApiResponse ? rawData.meta : void 0,
|
|
55
|
+
rawResponse: rawData,
|
|
56
|
+
loading: false,
|
|
57
|
+
success: true,
|
|
58
|
+
error: null,
|
|
59
|
+
message: isStandardApiResponse ? rawData.message : "Request successful.",
|
|
60
|
+
validationErrors: []
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (isServerError(responseOrError)) {
|
|
64
|
+
const error = responseOrError;
|
|
65
|
+
const responseData = error.response.data;
|
|
66
|
+
const status = error.response.status;
|
|
67
|
+
let defaultMessage = "An unexpected server error occurred.";
|
|
68
|
+
switch (status) {
|
|
69
|
+
case 400:
|
|
70
|
+
defaultMessage = "Bad Request";
|
|
71
|
+
break;
|
|
72
|
+
case 401:
|
|
73
|
+
defaultMessage = "Unauthorized";
|
|
74
|
+
break;
|
|
75
|
+
case 403:
|
|
76
|
+
defaultMessage = "Forbidden";
|
|
77
|
+
break;
|
|
78
|
+
case 404:
|
|
79
|
+
defaultMessage = "Not Found";
|
|
80
|
+
break;
|
|
81
|
+
case 422:
|
|
82
|
+
defaultMessage = "Validation Failed";
|
|
83
|
+
break;
|
|
84
|
+
case 500:
|
|
85
|
+
defaultMessage = "Internal Server Error";
|
|
86
|
+
break;
|
|
87
|
+
case 503:
|
|
88
|
+
defaultMessage = "Service Unavailable";
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
defaultMessage = `Server Error (${status})`;
|
|
92
|
+
}
|
|
93
|
+
const finalApiError = {
|
|
94
|
+
message: responseData?.message || defaultMessage,
|
|
95
|
+
status,
|
|
96
|
+
code: responseData?.code || error.code,
|
|
97
|
+
errors: responseData?.errors || []
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
data: null,
|
|
101
|
+
rawResponse: responseData,
|
|
102
|
+
error: finalApiError,
|
|
103
|
+
validationErrors: finalApiError.errors,
|
|
104
|
+
success: false,
|
|
105
|
+
loading: false,
|
|
106
|
+
message: finalApiError.message
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (isNetworkError(responseOrError)) {
|
|
110
|
+
const error = responseOrError;
|
|
111
|
+
return {
|
|
112
|
+
data: null,
|
|
113
|
+
rawResponse: error.request,
|
|
114
|
+
error: { message: "Network Error: Unable to connect.", status: 0, code: error.code },
|
|
115
|
+
success: false,
|
|
116
|
+
loading: false,
|
|
117
|
+
message: "Network Error."
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (axios2.isCancel(responseOrError)) {
|
|
121
|
+
return {
|
|
122
|
+
data: null,
|
|
123
|
+
rawResponse: null,
|
|
124
|
+
error: { message: "Request Canceled.", status: 499 },
|
|
125
|
+
success: false,
|
|
126
|
+
loading: false,
|
|
127
|
+
message: "Request Canceled."
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
data: null,
|
|
132
|
+
rawResponse: responseOrError,
|
|
133
|
+
error: { message: "An unknown error occurred.", status: -1 },
|
|
134
|
+
success: false,
|
|
135
|
+
loading: false,
|
|
136
|
+
message: "An unknown error occurred."
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/core/buildDynamicUrl.ts
|
|
141
|
+
function buildDynamicUrl(template, params) {
|
|
142
|
+
if (!params) {
|
|
143
|
+
return template;
|
|
144
|
+
}
|
|
145
|
+
return template.replace(/\{(\w+)\}/g, (placeholder, key) => {
|
|
146
|
+
return params.hasOwnProperty(key) ? String(params[key]) : placeholder;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/services/crud.ts
|
|
151
|
+
function createApiServices(axiosInstance, baseEndpoint) {
|
|
152
|
+
const resolveUrl = (config = {}, id) => {
|
|
153
|
+
const endpointTemplate = config.endpoint || (id != null ? `${baseEndpoint}/{id}` : baseEndpoint);
|
|
154
|
+
const params = id != null ? { id, tenant: id, tenantId: id, recordId: id } : {};
|
|
155
|
+
return buildDynamicUrl(endpointTemplate, params);
|
|
156
|
+
};
|
|
157
|
+
const get = async (id, config) => {
|
|
158
|
+
const url = resolveUrl(config, id);
|
|
159
|
+
try {
|
|
160
|
+
const response = await axiosInstance.get(url, config);
|
|
161
|
+
return processResponse(response);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return processResponse(error);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const getWithQuery = async (query, config) => {
|
|
167
|
+
const url = `${baseEndpoint}?${query}`;
|
|
168
|
+
try {
|
|
169
|
+
const response = await axiosInstance.get(url, config);
|
|
170
|
+
return processResponse(response);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return processResponse(error);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const post = async (data, config) => {
|
|
176
|
+
const url = resolveUrl(config);
|
|
177
|
+
try {
|
|
178
|
+
const response = await axiosInstance.post(url, data, config);
|
|
179
|
+
console.log("[lib] response POST: ", response);
|
|
180
|
+
console.log("[lib] response processResponse POST: ", processResponse(response));
|
|
181
|
+
return processResponse(response);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.log("[lib] response error POST: ", processResponse(error));
|
|
184
|
+
return processResponse(error);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const put = async (id, data, config) => {
|
|
188
|
+
const url = resolveUrl(config, id);
|
|
189
|
+
try {
|
|
190
|
+
const response = await axiosInstance.put(url, data, config);
|
|
191
|
+
return processResponse(response);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return processResponse(error);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const patch = async (id, data, config) => {
|
|
197
|
+
const url = resolveUrl(config, id);
|
|
198
|
+
try {
|
|
199
|
+
const response = await axiosInstance.patch(url, data, config);
|
|
200
|
+
return processResponse(response);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return processResponse(error);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const remove = async (id, config) => {
|
|
206
|
+
const url = resolveUrl(config, id);
|
|
207
|
+
try {
|
|
208
|
+
const response = await axiosInstance.delete(url, config);
|
|
209
|
+
return processResponse(response);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return processResponse(error);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
const bulkDelete = async (ids, config) => {
|
|
215
|
+
const url = resolveUrl(config);
|
|
216
|
+
try {
|
|
217
|
+
const response = await axiosInstance.delete(url, { data: { ids }, ...config });
|
|
218
|
+
return processResponse(response);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return processResponse(error);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const upload = async (file, additionalData, config) => {
|
|
224
|
+
const url = resolveUrl(config);
|
|
225
|
+
const formData = new FormData();
|
|
226
|
+
formData.append("file", file);
|
|
227
|
+
if (additionalData) {
|
|
228
|
+
Object.keys(additionalData).forEach((key) => formData.append(key, String(additionalData[key])));
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const response = await axiosInstance.post(url, formData, {
|
|
232
|
+
...config,
|
|
233
|
+
headers: { ...config?.headers, "Content-Type": "multipart/form-data" }
|
|
234
|
+
});
|
|
235
|
+
return processResponse(response);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return processResponse(error);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
return { get, getWithQuery, post, put, patch, remove, bulkDelete, upload };
|
|
241
|
+
}
|
|
242
|
+
async function callDynamicApi(axiosInstance, baseEndpoint, actionConfig, params) {
|
|
243
|
+
const { pathParams, body, config } = params;
|
|
244
|
+
const urlTemplate = `${baseEndpoint}${actionConfig.path}`;
|
|
245
|
+
const finalUrl = buildDynamicUrl(urlTemplate, pathParams || {});
|
|
246
|
+
try {
|
|
247
|
+
const { method } = actionConfig;
|
|
248
|
+
let response;
|
|
249
|
+
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
|
250
|
+
response = await axiosInstance[method.toLowerCase()](finalUrl, body, config);
|
|
251
|
+
} else if (method === "DELETE") {
|
|
252
|
+
response = await axiosInstance.delete(finalUrl, { data: body, ...config });
|
|
253
|
+
} else {
|
|
254
|
+
response = await axiosInstance.get(finalUrl, { params: body, ...config });
|
|
255
|
+
}
|
|
256
|
+
return processResponse(response);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return processResponse(error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/core/globalStateManager.ts
|
|
263
|
+
var createInitialState = () => ({
|
|
264
|
+
data: null,
|
|
265
|
+
links: void 0,
|
|
266
|
+
meta: void 0,
|
|
267
|
+
error: null,
|
|
268
|
+
loading: false,
|
|
269
|
+
success: false,
|
|
270
|
+
called: false,
|
|
271
|
+
isStale: false,
|
|
272
|
+
message: void 0,
|
|
273
|
+
validationErrors: [],
|
|
274
|
+
rawResponse: null
|
|
275
|
+
});
|
|
276
|
+
var GlobalStateManager = class {
|
|
277
|
+
constructor() {
|
|
278
|
+
__publicField(this, "store", /* @__PURE__ */ new Map());
|
|
279
|
+
}
|
|
280
|
+
getSnapshot(key) {
|
|
281
|
+
if (!this.store.has(key)) {
|
|
282
|
+
const initialState = createInitialState();
|
|
283
|
+
this.store.set(key, { state: initialState, listeners: /* @__PURE__ */ new Set() });
|
|
284
|
+
}
|
|
285
|
+
return this.store.get(key).state;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* يسجل دالة callback للاستماع إلى التغييرات على مفتاح معين.
|
|
289
|
+
* @returns دالة لإلغاء الاشتراك.
|
|
290
|
+
*/
|
|
291
|
+
subscribe(key, callback) {
|
|
292
|
+
if (!this.store.has(key)) {
|
|
293
|
+
this.store.set(key, { state: createInitialState(), listeners: /* @__PURE__ */ new Set() });
|
|
294
|
+
}
|
|
295
|
+
const item = this.store.get(key);
|
|
296
|
+
item.listeners.add(callback);
|
|
297
|
+
return () => {
|
|
298
|
+
item.listeners.delete(callback);
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* يحدّث الحالة لمفتاح معين ويقوم بإعلام جميع المشتركين.
|
|
303
|
+
*/
|
|
304
|
+
setState(key, updater) {
|
|
305
|
+
const currentState = this.getSnapshot(key);
|
|
306
|
+
const newState = updater(currentState);
|
|
307
|
+
if (Object.is(currentState, newState)) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const item = this.store.get(key);
|
|
311
|
+
item.state = newState;
|
|
312
|
+
item.listeners.forEach((listener) => listener());
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* يجعل البيانات المرتبطة بمفتاح معين "قديمة" (stale).
|
|
316
|
+
*/
|
|
317
|
+
invalidate(key) {
|
|
318
|
+
const state = this.getSnapshot(key);
|
|
319
|
+
if (state.called) {
|
|
320
|
+
this.setState(key, (prev) => ({ ...prev, isStale: true }));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* [نسخة محدثة وأكثر قوة]
|
|
325
|
+
* يجعل كل البيانات التي تبدأ بمفتاح معين "قديمة" (stale).
|
|
326
|
+
* @example invalidateByPrefix('myModule/list::') سيبطل كل صفحات القائمة.
|
|
327
|
+
*/
|
|
328
|
+
invalidateByPrefix(prefix) {
|
|
329
|
+
this.store.forEach((value, key) => {
|
|
330
|
+
if (key.startsWith(prefix)) {
|
|
331
|
+
const state = this.getSnapshot(key);
|
|
332
|
+
if (state.called) {
|
|
333
|
+
this.setState(key, (prev) => ({ ...prev, isStale: true }));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Serializes the current state of the query store into a JSON string.
|
|
340
|
+
* This is used on the server to pass the initial state to the client.
|
|
341
|
+
* @returns A JSON string representing the dehydrated state.
|
|
342
|
+
*/
|
|
343
|
+
dehydrate() {
|
|
344
|
+
const stateToPersist = {};
|
|
345
|
+
this.store.forEach((value, key) => {
|
|
346
|
+
if (value.state.called) {
|
|
347
|
+
stateToPersist[key] = value.state;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
return JSON.stringify(stateToPersist);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Merges a dehydrated state object into the current store.
|
|
354
|
+
* This is used on the client to hydrate the state received from the server.
|
|
355
|
+
* @param hydratedState - A JSON string from the `dehydrate` method.
|
|
356
|
+
*/
|
|
357
|
+
rehydrate(hydratedState) {
|
|
358
|
+
try {
|
|
359
|
+
const parsedState = JSON.parse(hydratedState);
|
|
360
|
+
for (const key in parsedState) {
|
|
361
|
+
this.setState(key, () => parsedState[key]);
|
|
362
|
+
}
|
|
363
|
+
} catch (e) {
|
|
364
|
+
console.error("[api-core-lib] Failed to rehydrate state:", e);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
var globalStateManager = new GlobalStateManager();
|
|
369
|
+
|
|
370
|
+
// src/core/cacheKey.ts
|
|
371
|
+
var generateCacheKey = (moduleName, actionName, input, callOptions = {}) => {
|
|
372
|
+
const params = { path: callOptions.pathParams, body: input };
|
|
373
|
+
try {
|
|
374
|
+
return `${moduleName}/${actionName}::${JSON.stringify(params)}`;
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.warn("Could not stringify cache key params, falling back to timestamp.", { moduleName, actionName, error });
|
|
377
|
+
return `${moduleName}/${actionName}::${Date.now()}`;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export {
|
|
382
|
+
buildPaginateQuery,
|
|
383
|
+
processResponse,
|
|
384
|
+
createApiServices,
|
|
385
|
+
callDynamicApi,
|
|
386
|
+
globalStateManager,
|
|
387
|
+
generateCacheKey
|
|
388
|
+
};
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
import "./chunk-NRV3EMIJ.js";
|
|
4
|
+
|
|
5
|
+
// src/cli.ts
|
|
6
|
+
import { program } from "commander";
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
|
|
9
|
+
// src/generator/index.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import axios from "axios";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import dotenv from "dotenv";
|
|
15
|
+
import openapiTS from "openapi-typescript";
|
|
16
|
+
|
|
17
|
+
// src/generator/module-parser.ts
|
|
18
|
+
function parseSpecToModules(spec) {
|
|
19
|
+
const modules = {};
|
|
20
|
+
for (const apiPath in spec.paths) {
|
|
21
|
+
for (const method in spec.paths[apiPath]) {
|
|
22
|
+
const endpoint = spec.paths[apiPath][method];
|
|
23
|
+
if (!endpoint.tags || endpoint.tags.length === 0) continue;
|
|
24
|
+
const tagName = endpoint.tags[0];
|
|
25
|
+
const moduleName = tagName.replace(/[^a-zA-Z0-9]/g, "").replace(/Central|Tenant/g, "") + "Api";
|
|
26
|
+
if (!modules[moduleName]) {
|
|
27
|
+
modules[moduleName] = {
|
|
28
|
+
baseEndpoint: "",
|
|
29
|
+
// سيتم ملء المسار الكامل في path
|
|
30
|
+
actions: {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const actionName = endpoint.operationId || method + apiPath.replace(/[\/{}]+/g, "_");
|
|
34
|
+
modules[moduleName].actions[actionName] = {
|
|
35
|
+
method: method.toUpperCase(),
|
|
36
|
+
path: apiPath,
|
|
37
|
+
description: endpoint.summary || "No description available.",
|
|
38
|
+
hasQuery: (endpoint.parameters || []).some((p) => p.in === "query"),
|
|
39
|
+
autoFetch: method.toUpperCase() === "GET" && !apiPath.includes("{"),
|
|
40
|
+
// افتراض ذكي
|
|
41
|
+
invalidates: []
|
|
42
|
+
// يمكن تركه فارغًا
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return modules;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/generator/index.ts
|
|
50
|
+
async function runGenerator(options) {
|
|
51
|
+
console.log(chalk.cyan(`Starting API module generation...`));
|
|
52
|
+
console.log(chalk.gray(`Output directory: ${options.output}`));
|
|
53
|
+
console.log(chalk.gray(`.env path: ${options.envPath}`));
|
|
54
|
+
dotenv.config({ path: options.envPath });
|
|
55
|
+
const apiUrl = process.env.NEXT_PUBLIC_API_URL || process.env.API_URL;
|
|
56
|
+
if (!apiUrl) {
|
|
57
|
+
console.error(chalk.red("Error: API_URL or NEXT_PUBLIC_API_URL not found in .env file."));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const specUrl = `${apiUrl}/api-docs/json`;
|
|
62
|
+
console.log(`Fetching OpenAPI spec from ${specUrl}...`);
|
|
63
|
+
const response = await axios.get(specUrl);
|
|
64
|
+
const spec = response.data;
|
|
65
|
+
if (!fs.existsSync(options.output)) {
|
|
66
|
+
fs.mkdirSync(options.output, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
console.log("Generating TypeScript types...");
|
|
69
|
+
const typesContent = await openapiTS(spec);
|
|
70
|
+
const typesPath = path.join(options.output, "api-types.generated.ts");
|
|
71
|
+
fs.writeFileSync(typesPath, typesContent);
|
|
72
|
+
console.log(chalk.green(`\u2713 Types generated successfully at ${typesPath}`));
|
|
73
|
+
console.log("Generating API modules...");
|
|
74
|
+
const modules = parseSpecToModules(spec);
|
|
75
|
+
for (const moduleName in modules) {
|
|
76
|
+
const moduleConfig = modules[moduleName];
|
|
77
|
+
const fileName = `${moduleName}.module.ts`;
|
|
78
|
+
const filePath = path.join(options.output, fileName);
|
|
79
|
+
const fileContent = `
|
|
80
|
+
// This file is auto-generated by api-core-lib.
|
|
81
|
+
// Do not edit this file directly.
|
|
82
|
+
|
|
83
|
+
import type { ApiModuleConfig } from 'api-core-lib';
|
|
84
|
+
import type { paths } from './api-types.generated'; // \u0631\u0628\u0637 \u0627\u0644\u0623\u0646\u0648\u0627\u0639 \u0627\u0644\u0645\u0648\u0644\u062F\u0629
|
|
85
|
+
|
|
86
|
+
// A more advanced generator could automatically map the correct input/output types.
|
|
87
|
+
// For now, we use 'any' as a placeholder.
|
|
88
|
+
export const ${moduleName}Module: ApiModuleConfig<any> = ${JSON.stringify(moduleConfig, null, 2)};
|
|
89
|
+
`;
|
|
90
|
+
fs.writeFileSync(filePath, fileContent);
|
|
91
|
+
console.log(chalk.green(`\u2713 Module generated: ${fileName}`));
|
|
92
|
+
}
|
|
93
|
+
console.log(chalk.bold.green("\n\u{1F389} API generation complete!"));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(chalk.red("\nAn error occurred during generation:"));
|
|
96
|
+
console.error(error.message);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/cli.ts
|
|
102
|
+
console.log("API Core Lib - Code Generator");
|
|
103
|
+
program.option("-o, --output <path>", "Output directory for generated files", "src/lib/api-generated").option("--env-path <path>", ".env file path", path2.resolve(process.cwd(), ".env")).action((options) => {
|
|
104
|
+
const absoluteOutputPath = path2.resolve(process.cwd(), options.output);
|
|
105
|
+
runGenerator({ ...options, output: absoluteOutputPath });
|
|
106
|
+
});
|
|
107
|
+
program.parse(process.argv);
|