@currentjs/gen 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -611
- package/README.md +623 -427
- package/dist/cli.js +2 -1
- package/dist/commands/commit.js +25 -42
- package/dist/commands/createApp.js +1 -0
- package/dist/commands/createModule.js +151 -45
- package/dist/commands/diff.js +27 -40
- package/dist/commands/generateAll.js +141 -291
- package/dist/commands/migrateCommit.js +6 -18
- package/dist/commands/migratePush.d.ts +1 -0
- package/dist/commands/migratePush.js +135 -0
- package/dist/commands/migrateUpdate.d.ts +1 -0
- package/dist/commands/migrateUpdate.js +147 -0
- package/dist/commands/newGenerateAll.d.ts +4 -0
- package/dist/commands/newGenerateAll.js +336 -0
- package/dist/generators/controllerGenerator.d.ts +43 -19
- package/dist/generators/controllerGenerator.js +547 -329
- package/dist/generators/domainLayerGenerator.d.ts +21 -0
- package/dist/generators/domainLayerGenerator.js +276 -0
- package/dist/generators/dtoGenerator.d.ts +21 -0
- package/dist/generators/dtoGenerator.js +518 -0
- package/dist/generators/newControllerGenerator.d.ts +55 -0
- package/dist/generators/newControllerGenerator.js +644 -0
- package/dist/generators/newServiceGenerator.d.ts +19 -0
- package/dist/generators/newServiceGenerator.js +266 -0
- package/dist/generators/newStoreGenerator.d.ts +39 -0
- package/dist/generators/newStoreGenerator.js +408 -0
- package/dist/generators/newTemplateGenerator.d.ts +29 -0
- package/dist/generators/newTemplateGenerator.js +510 -0
- package/dist/generators/serviceGenerator.d.ts +16 -51
- package/dist/generators/serviceGenerator.js +167 -586
- package/dist/generators/storeGenerator.d.ts +35 -32
- package/dist/generators/storeGenerator.js +291 -238
- package/dist/generators/storeGeneratorV2.d.ts +31 -0
- package/dist/generators/storeGeneratorV2.js +190 -0
- package/dist/generators/templateGenerator.d.ts +21 -21
- package/dist/generators/templateGenerator.js +393 -268
- package/dist/generators/templates/appTemplates.d.ts +3 -1
- package/dist/generators/templates/appTemplates.js +15 -10
- package/dist/generators/templates/data/appYamlTemplate +5 -2
- package/dist/generators/templates/data/cursorRulesTemplate +315 -221
- package/dist/generators/templates/data/frontendScriptTemplate +45 -11
- package/dist/generators/templates/data/mainViewTemplate +1 -1
- package/dist/generators/templates/data/systemTsTemplate +5 -0
- package/dist/generators/templates/index.d.ts +0 -3
- package/dist/generators/templates/index.js +0 -3
- package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
- package/dist/generators/templates/newStoreTemplates.js +141 -0
- package/dist/generators/templates/storeTemplates.d.ts +1 -5
- package/dist/generators/templates/storeTemplates.js +102 -219
- package/dist/generators/templates/viewTemplates.js +1 -1
- package/dist/generators/useCaseGenerator.d.ts +13 -0
- package/dist/generators/useCaseGenerator.js +188 -0
- package/dist/types/configTypes.d.ts +148 -0
- package/dist/types/configTypes.js +10 -0
- package/dist/utils/childEntityUtils.d.ts +18 -0
- package/dist/utils/childEntityUtils.js +78 -0
- package/dist/utils/commandUtils.d.ts +43 -0
- package/dist/utils/commandUtils.js +124 -0
- package/dist/utils/commitUtils.d.ts +4 -1
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +13 -1
- package/dist/utils/diResolver.d.ts +32 -0
- package/dist/utils/diResolver.js +204 -0
- package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
- package/dist/utils/new_parts_of_migrationUtils.js +164 -0
- package/dist/utils/typeUtils.d.ts +19 -0
- package/dist/utils/typeUtils.js +70 -0
- package/package.json +7 -3
|
@@ -37,640 +37,221 @@ exports.ServiceGenerator = void 0;
|
|
|
37
37
|
const yaml_1 = require("yaml");
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
-
const serviceTemplates_1 = require("./templates/serviceTemplates");
|
|
41
40
|
const generationRegistry_1 = require("../utils/generationRegistry");
|
|
42
41
|
const colors_1 = require("../utils/colors");
|
|
43
|
-
const
|
|
42
|
+
const configTypes_1 = require("../types/configTypes");
|
|
43
|
+
const childEntityUtils_1 = require("../utils/childEntityUtils");
|
|
44
|
+
const typeUtils_1 = require("../utils/typeUtils");
|
|
44
45
|
class ServiceGenerator {
|
|
45
46
|
constructor() {
|
|
46
|
-
this.
|
|
47
|
+
this.availableAggregates = new Map();
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
this.
|
|
50
|
-
models.forEach(model => {
|
|
51
|
-
this.availableModels.add(model.name);
|
|
52
|
-
});
|
|
49
|
+
mapType(yamlType) {
|
|
50
|
+
return (0, typeUtils_1.mapType)(yamlType, this.availableAggregates);
|
|
53
51
|
}
|
|
54
|
-
|
|
55
|
-
return
|
|
52
|
+
generateListHandler(modelName, storeName) {
|
|
53
|
+
return ` async list(page: number = 1, limit: number = 20): Promise<{ items: ${modelName}[]; total: number; page: number; limit: number }> {
|
|
54
|
+
const [items, total] = await Promise.all([
|
|
55
|
+
this.${storeName}.getAll(page, limit),
|
|
56
|
+
this.${storeName}.count()
|
|
57
|
+
]);
|
|
58
|
+
return { items, total, page, limit };
|
|
59
|
+
}`;
|
|
56
60
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
generateGetHandler(modelName, storeName, entityLower) {
|
|
62
|
+
return ` async get(id: number): Promise<${modelName}> {
|
|
63
|
+
const ${entityLower} = await this.${storeName}.getById(id);
|
|
64
|
+
if (!${entityLower}) {
|
|
65
|
+
throw new Error('${modelName} not found');
|
|
60
66
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return Object.values(config.modules).some(module => module.permissions && module.permissions.length > 0);
|
|
64
|
-
}
|
|
65
|
-
const module = config;
|
|
66
|
-
return !!(module.permissions && module.permissions.length > 0);
|
|
67
|
+
return ${entityLower};
|
|
68
|
+
}`;
|
|
67
69
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo) {
|
|
71
|
+
const firstArgField = childInfo ? childInfo.parentIdField : 'ownerId';
|
|
72
|
+
const fields = Object.entries(aggregateConfig.fields)
|
|
73
|
+
.filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id')
|
|
74
|
+
.sort((a, b) => {
|
|
75
|
+
const aRequired = a[1].required !== false;
|
|
76
|
+
const bRequired = b[1].required !== false;
|
|
77
|
+
if (aRequired === bRequired)
|
|
78
|
+
return 0;
|
|
79
|
+
return aRequired ? -1 : 1;
|
|
76
80
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
return actionPermissions;
|
|
86
|
-
}
|
|
87
|
-
generatePermissionCheck(action, roles, entityName) {
|
|
88
|
-
if (roles.length === 0 || roles.includes('all')) {
|
|
89
|
-
return '';
|
|
90
|
-
}
|
|
91
|
-
if (roles.includes('none')) {
|
|
92
|
-
return " throw new Error('This action is not permitted');";
|
|
93
|
-
}
|
|
94
|
-
const nonOwnerRoles = roles.filter(role => !['all', 'none', 'owner'].includes(role));
|
|
95
|
-
const rolesArray = nonOwnerRoles.map(role => `'${role}'`).join(', ');
|
|
96
|
-
// Special-case: list action with owner → rely on store-level filtering; only enforce static role checks for other roles
|
|
97
|
-
if (action === 'list' && roles.includes('owner')) {
|
|
98
|
-
if (nonOwnerRoles.length === 0) {
|
|
99
|
-
return '';
|
|
100
|
-
}
|
|
101
|
-
return serviceTemplates_1.serviceTemplates.permissionCheck
|
|
102
|
-
.replace(/{{REQUIRED_ROLES}}/g, roles.join(', '))
|
|
103
|
-
.replace(/{{ACTION_NAME}}/g, action)
|
|
104
|
-
.replace(/{{ROLES_ARRAY}}/g, rolesArray);
|
|
105
|
-
}
|
|
106
|
-
// For resource-based owner permission (non-list), fetch resource first and verify ownership
|
|
107
|
-
if (roles.includes('owner')) {
|
|
108
|
-
const isResourceBased = ['get', 'update', 'delete'].includes(action);
|
|
109
|
-
if (!isResourceBased) {
|
|
110
|
-
// Not a resource-based action; fall back to static role checks only (if any non-owner roles)
|
|
111
|
-
if (nonOwnerRoles.length === 0) {
|
|
112
|
-
return '';
|
|
113
|
-
}
|
|
114
|
-
return serviceTemplates_1.serviceTemplates.permissionCheck
|
|
115
|
-
.replace(/{{REQUIRED_ROLES}}/g, roles.join(', '))
|
|
116
|
-
.replace(/{{ACTION_NAME}}/g, action)
|
|
117
|
-
.replace(/{{ROLES_ARRAY}}/g, rolesArray);
|
|
118
|
-
}
|
|
119
|
-
const entityLower = entityName.toLowerCase();
|
|
120
|
-
const resourceIdParam = this.getResourceIdForAction(action);
|
|
121
|
-
const notFoundMessage = `${entityName} not found`;
|
|
122
|
-
// If there are additional roles besides owner, allow them to bypass owner check
|
|
123
|
-
const maybeRoleBypass = nonOwnerRoles.length > 0
|
|
124
|
-
? ` const allowedRoles = [${rolesArray}];
|
|
125
|
-
if (allowedRoles.includes(user.role)) {
|
|
126
|
-
// Explicit role allowed; skip owner check
|
|
127
|
-
} else {
|
|
128
|
-
const ${entityLower} = await this.${entityLower}Store.getById(${resourceIdParam});
|
|
129
|
-
if (!${entityLower}) {
|
|
130
|
-
throw new Error('${notFoundMessage}');
|
|
131
|
-
}
|
|
132
|
-
if ((${entityLower} as any).userId !== user.id) {
|
|
133
|
-
throw new Error('You can only access your own resources');
|
|
134
|
-
}
|
|
135
|
-
}`
|
|
136
|
-
: ` const ${entityLower} = await this.${entityLower}Store.getById(${resourceIdParam});
|
|
137
|
-
if (!${entityLower}) {
|
|
138
|
-
throw new Error('${notFoundMessage}');
|
|
81
|
+
const fieldArgs = fields.map(([fieldName]) => `input.${fieldName}`).join(', ');
|
|
82
|
+
const constructorArgs = `input.${firstArgField}, ${fieldArgs}`;
|
|
83
|
+
return ` async create(input: any): Promise<${modelName}> {
|
|
84
|
+
const ${entityLower} = new ${modelName}(0, ${constructorArgs});
|
|
85
|
+
return await this.${storeName}.insert(${entityLower});
|
|
86
|
+
}`;
|
|
139
87
|
}
|
|
140
|
-
|
|
141
|
-
|
|
88
|
+
generateUpdateHandler(modelName, storeName, aggregateConfig) {
|
|
89
|
+
const setterCalls = Object.entries(aggregateConfig.fields)
|
|
90
|
+
.filter(([fieldName, fieldConfig]) => !fieldConfig.auto && fieldName !== 'id')
|
|
91
|
+
.map(([fieldName]) => {
|
|
92
|
+
const methodName = `set${(0, typeUtils_1.capitalize)(fieldName)}`;
|
|
93
|
+
return ` if (input.${fieldName} !== undefined) {
|
|
94
|
+
existing${modelName}.${methodName}(input.${fieldName});
|
|
142
95
|
}`;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.replace(/{{ROLES_ARRAY}}/g, rolesArray);
|
|
150
|
-
}
|
|
151
|
-
getResourceIdForAction(action) {
|
|
152
|
-
switch (action) {
|
|
153
|
-
case 'get':
|
|
154
|
-
case 'update':
|
|
155
|
-
case 'delete':
|
|
156
|
-
return 'id';
|
|
157
|
-
default:
|
|
158
|
-
return 'id';
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
generateMethodParams(action, entityName) {
|
|
162
|
-
const entityParam = `${entityName.toLowerCase()}Data`;
|
|
163
|
-
switch (action) {
|
|
164
|
-
case 'list':
|
|
165
|
-
return 'page: number = 1, limit: number = 10';
|
|
166
|
-
case 'get':
|
|
167
|
-
case 'getById':
|
|
168
|
-
return 'id: number';
|
|
169
|
-
case 'create':
|
|
170
|
-
return `${entityParam}: ${entityName}DTO`;
|
|
171
|
-
case 'update':
|
|
172
|
-
return `id: number, ${entityParam}: ${entityName}DTO`;
|
|
173
|
-
case 'delete':
|
|
174
|
-
return 'id: number';
|
|
175
|
-
default:
|
|
176
|
-
return '';
|
|
177
|
-
}
|
|
96
|
+
})
|
|
97
|
+
.join('\n');
|
|
98
|
+
return ` async update(id: number, input: any): Promise<${modelName}> {
|
|
99
|
+
const existing${modelName} = await this.${storeName}.getById(id);
|
|
100
|
+
if (!existing${modelName}) {
|
|
101
|
+
throw new Error('${modelName} not found');
|
|
178
102
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
case 'getById':
|
|
185
|
-
case 'create':
|
|
186
|
-
case 'update':
|
|
187
|
-
return entityName;
|
|
188
|
-
case 'delete':
|
|
189
|
-
return '{ success: boolean; message: string }';
|
|
190
|
-
default:
|
|
191
|
-
return 'void';
|
|
192
|
-
}
|
|
103
|
+
|
|
104
|
+
${setterCalls}
|
|
105
|
+
|
|
106
|
+
return await this.${storeName}.update(id, existing${modelName});
|
|
107
|
+
}`;
|
|
193
108
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const entityLower = entityName.toLowerCase();
|
|
200
|
-
handlers.forEach(handler => {
|
|
201
|
-
var _a, _b;
|
|
202
|
-
// Handle new explicit format: modelname:default:action or modelname:custommethod
|
|
203
|
-
if (handler.includes(':')) {
|
|
204
|
-
const parts = handler.split(':');
|
|
205
|
-
if (parts.length === 3 && parts[1] === 'default') {
|
|
206
|
-
// Format: modelname:default:action
|
|
207
|
-
const [modelName, , actionName] = parts;
|
|
208
|
-
// Check if this handler applies to current model
|
|
209
|
-
if (modelName.toLowerCase() === entityLower || modelName === entityName) {
|
|
210
|
-
const template = serviceTemplates_1.serviceTemplates.defaultImplementations[actionName];
|
|
211
|
-
if (template) {
|
|
212
|
-
let processedTemplate = template
|
|
213
|
-
.replace(/{{ENTITY_NAME}}/g, entityName)
|
|
214
|
-
.replace(/{{ENTITY_LOWER}}/g, entityLower);
|
|
215
|
-
// Handle constructor args for create action
|
|
216
|
-
if (actionName === 'create') {
|
|
217
|
-
const relationshipLoading = this.generateRelationshipLoading(moduleConfig, entityName);
|
|
218
|
-
const constructorArgs = this.generateConstructorArgs(moduleConfig, entityName);
|
|
219
|
-
if (relationshipLoading) {
|
|
220
|
-
// Insert relationship loading before the entity creation
|
|
221
|
-
processedTemplate = relationshipLoading + '\n ' + processedTemplate;
|
|
222
|
-
}
|
|
223
|
-
processedTemplate = processedTemplate.replace(/{{CONSTRUCTOR_ARGS}}/g, constructorArgs);
|
|
224
|
-
}
|
|
225
|
-
// Handle setter calls for update action
|
|
226
|
-
if (actionName === 'update') {
|
|
227
|
-
const setterCalls = this.generateUpdateSetterCalls(moduleConfig, entityName, true);
|
|
228
|
-
processedTemplate = processedTemplate.replace(/{{UPDATE_SETTER_CALLS}}/g, setterCalls);
|
|
229
|
-
}
|
|
230
|
-
// Special-case: list action with only owner role → fetch by userId
|
|
231
|
-
if (actionName === 'list') {
|
|
232
|
-
const onlyOwner = roles.length > 0 && roles.every(r => r === 'owner');
|
|
233
|
-
if (onlyOwner) {
|
|
234
|
-
processedTemplate = `const ${entityLower}s = await this.${entityLower}Store.getAllByUserId(user.id, page, limit);\n return ${entityLower}s;`;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
implementations.push(processedTemplate);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
implementations.push(`// TODO: Implement default ${actionName} method for ${entityName}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
// Cross-model default call
|
|
245
|
-
const targetModel = (_a = moduleConfig.models) === null || _a === void 0 ? void 0 : _a.find(m => m.name.toLowerCase() === modelName.toLowerCase() || m.name === modelName);
|
|
246
|
-
if (targetModel) {
|
|
247
|
-
const targetServiceVar = `${targetModel.name.toLowerCase()}Service`;
|
|
248
|
-
const customImpl = `// Cross-model call to ${targetModel.name}Service
|
|
249
|
-
const result = await this.${targetServiceVar}.${actionName}(${this.getMethodCallParams(action)});
|
|
250
|
-
return result;`;
|
|
251
|
-
implementations.push(customImpl);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
implementations.push(`// TODO: Model ${modelName} not found for handler ${handler}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
else if (parts.length === 2) {
|
|
259
|
-
// Format: modelname:custommethod
|
|
260
|
-
const [modelName, methodName] = parts;
|
|
261
|
-
// Check if this handler applies to current model
|
|
262
|
-
if (modelName.toLowerCase() === entityLower || modelName === entityName) {
|
|
263
|
-
// Same model - this indicates a custom method should be implemented in this service
|
|
264
|
-
implementations.push(`// TODO: Implement custom ${methodName} method for ${entityName}`);
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
// Cross-model custom call
|
|
268
|
-
const targetModel = (_b = moduleConfig.models) === null || _b === void 0 ? void 0 : _b.find(m => m.name.toLowerCase() === modelName.toLowerCase() || m.name === modelName);
|
|
269
|
-
if (targetModel) {
|
|
270
|
-
const targetServiceVar = `${targetModel.name.toLowerCase()}Service`;
|
|
271
|
-
const customImpl = `// Cross-model call to ${targetModel.name}Service
|
|
272
|
-
const result = await this.${targetServiceVar}.${methodName}(${this.getMethodCallParams(action)});
|
|
273
|
-
return result;`;
|
|
274
|
-
implementations.push(customImpl);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
implementations.push(`// TODO: Model ${modelName} not found for handler ${handler}`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
implementations.push(`// TODO: Invalid handler format ${handler}. Use modelname:default:action or modelname:custommethod`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
implementations.push(`// TODO: Invalid handler format ${handler}. Use modelname:default:action or modelname:custommethod`);
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
return implementations.join('\n ');
|
|
109
|
+
generateDeleteHandler(modelName, storeName) {
|
|
110
|
+
return ` async delete(id: number): Promise<{ success: boolean; message: string }> {
|
|
111
|
+
const success = await this.${storeName}.softDelete(id);
|
|
112
|
+
if (!success) {
|
|
113
|
+
throw new Error('${modelName} not found or could not be deleted');
|
|
290
114
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const fileName = parts[parts.length - 1];
|
|
294
|
-
return fileName;
|
|
115
|
+
return { success: true, message: '${modelName} deleted successfully' };
|
|
116
|
+
}`;
|
|
295
117
|
}
|
|
296
|
-
|
|
297
|
-
|
|
118
|
+
generateDefaultHandlerMethod(modelName, actionName, aggregateConfig, childInfo) {
|
|
119
|
+
const entityLower = modelName.toLowerCase();
|
|
120
|
+
const storeName = `${entityLower}Store`;
|
|
121
|
+
switch (actionName) {
|
|
298
122
|
case 'list':
|
|
299
|
-
return
|
|
123
|
+
return this.generateListHandler(modelName, storeName);
|
|
300
124
|
case 'get':
|
|
301
|
-
return
|
|
125
|
+
return this.generateGetHandler(modelName, storeName, entityLower);
|
|
302
126
|
case 'create':
|
|
303
|
-
return
|
|
127
|
+
return this.generateCreateHandler(modelName, storeName, entityLower, aggregateConfig, childInfo);
|
|
304
128
|
case 'update':
|
|
305
|
-
return
|
|
129
|
+
return this.generateUpdateHandler(modelName, storeName, aggregateConfig);
|
|
306
130
|
case 'delete':
|
|
307
|
-
return
|
|
131
|
+
return this.generateDeleteHandler(modelName, storeName);
|
|
308
132
|
default:
|
|
309
|
-
return
|
|
133
|
+
return ` async ${actionName}(input: any): Promise<any> {
|
|
134
|
+
// TODO: Implement default ${actionName} handler
|
|
135
|
+
throw new Error('Not implemented');
|
|
136
|
+
}`;
|
|
310
137
|
}
|
|
311
138
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return 0; // Keep original order if both have same required status
|
|
320
|
-
}
|
|
321
|
-
return aRequired ? -1 : 1; // Required fields come first
|
|
322
|
-
});
|
|
139
|
+
generateCustomHandlerMethod(modelName, handlerName) {
|
|
140
|
+
return ` async ${handlerName}(result: any, input: any): Promise<any> {
|
|
141
|
+
// TODO: Implement custom ${handlerName} handler
|
|
142
|
+
// This method receives the result from the previous handler (or null if first)
|
|
143
|
+
// and the input context
|
|
144
|
+
return result;
|
|
145
|
+
}`;
|
|
323
146
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const entityLower = entityName.toLowerCase();
|
|
331
|
-
// Sort fields to match the constructor parameter order
|
|
332
|
-
const sortedFields = this.sortFieldsByRequired(model.fields);
|
|
333
|
-
const args = [];
|
|
334
|
-
sortedFields
|
|
335
|
-
.filter(field => !field.auto && field.name !== 'id')
|
|
336
|
-
.forEach(field => {
|
|
337
|
-
// For relationship fields, reference the loaded object variable
|
|
338
|
-
if (this.isRelationshipField(field)) {
|
|
339
|
-
args.push(`${field.name}Object`);
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
args.push(`${entityLower}Data.${field.name}`);
|
|
343
|
-
}
|
|
147
|
+
collectHandlers(useCases) {
|
|
148
|
+
const handlers = new Set();
|
|
149
|
+
Object.values(useCases).forEach(useCaseConfig => {
|
|
150
|
+
useCaseConfig.handlers.forEach(handler => {
|
|
151
|
+
handlers.add(handler);
|
|
152
|
+
});
|
|
344
153
|
});
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
generateRelationshipLoading(moduleConfig, entityName) {
|
|
348
|
-
if (!moduleConfig.models || moduleConfig.models.length === 0) {
|
|
349
|
-
return '';
|
|
350
|
-
}
|
|
351
|
-
const model = moduleConfig.models.find((m) => m.name === entityName) || moduleConfig.models[0];
|
|
352
|
-
const entityLower = entityName.toLowerCase();
|
|
353
|
-
const relationshipFields = model.fields.filter(f => this.isRelationshipField(f));
|
|
354
|
-
if (relationshipFields.length === 0) {
|
|
355
|
-
return '';
|
|
356
|
-
}
|
|
357
|
-
const loadingCode = relationshipFields.map(field => {
|
|
358
|
-
const foreignKeyName = this.getForeignKeyFieldName(field);
|
|
359
|
-
const relatedModel = field.type;
|
|
360
|
-
const relatedModelLower = relatedModel.toLowerCase();
|
|
361
|
-
const varName = `${field.name}Object`;
|
|
362
|
-
if (field.required) {
|
|
363
|
-
return `const ${varName} = await this.${relatedModelLower}Store.getById(${entityLower}Data.${foreignKeyName});
|
|
364
|
-
if (!${varName}) {
|
|
365
|
-
throw new Error('${relatedModel} not found with id ' + ${entityLower}Data.${foreignKeyName});
|
|
366
|
-
}`;
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
return `const ${varName} = ${entityLower}Data.${foreignKeyName}
|
|
370
|
-
? await this.${relatedModelLower}Store.getById(${entityLower}Data.${foreignKeyName})
|
|
371
|
-
: null;`;
|
|
372
|
-
}
|
|
373
|
-
}).join('\n ');
|
|
374
|
-
return ' // Load relationship objects\n ' + loadingCode;
|
|
154
|
+
return handlers;
|
|
375
155
|
}
|
|
376
|
-
|
|
377
|
-
if (!
|
|
156
|
+
generateListByParentMethod(modelName, childInfo) {
|
|
157
|
+
if (!childInfo)
|
|
378
158
|
return '';
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// Add relationship loading if requested
|
|
385
|
-
if (includeRelationshipLoading) {
|
|
386
|
-
const relationshipLoading = this.generateRelationshipLoading(moduleConfig, entityName);
|
|
387
|
-
if (relationshipLoading) {
|
|
388
|
-
code = relationshipLoading + '\n ';
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const setterCalls = model.fields
|
|
392
|
-
.filter(field => !field.auto && field.name !== 'id')
|
|
393
|
-
.map(field => {
|
|
394
|
-
// For relationship fields, set the loaded object
|
|
395
|
-
if (this.isRelationshipField(field)) {
|
|
396
|
-
const foreignKeyName = this.getForeignKeyFieldName(field);
|
|
397
|
-
const methodName = `set${field.name.charAt(0).toUpperCase() + field.name.slice(1)}`;
|
|
398
|
-
return `if (${entityLower}Data.${foreignKeyName} !== undefined) {
|
|
399
|
-
existing${entityName}.${methodName}(${field.name}Object);
|
|
400
|
-
}`;
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
const methodName = `set${field.name.charAt(0).toUpperCase() + field.name.slice(1)}`;
|
|
404
|
-
return `if (${entityLower}Data.${field.name} !== undefined) {
|
|
405
|
-
existing${entityName}.${methodName}(${entityLower}Data.${field.name});
|
|
406
|
-
}`;
|
|
407
|
-
}
|
|
408
|
-
})
|
|
409
|
-
.join('\n ');
|
|
410
|
-
return code + setterCalls;
|
|
411
|
-
}
|
|
412
|
-
replaceTemplateVars(template, variables) {
|
|
413
|
-
let result = template;
|
|
414
|
-
Object.entries(variables).forEach(([key, value]) => {
|
|
415
|
-
const regex = new RegExp(`{{${key}}}`, 'g');
|
|
416
|
-
result = result.replace(regex, String(value));
|
|
417
|
-
});
|
|
418
|
-
return result;
|
|
159
|
+
const storeVar = `${modelName.toLowerCase()}Store`;
|
|
160
|
+
return `
|
|
161
|
+
async listByParent(parentId: number): Promise<${modelName}[]> {
|
|
162
|
+
return await this.${storeVar}.getByParentId(parentId);
|
|
163
|
+
}`;
|
|
419
164
|
}
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const [modelName] = parts;
|
|
431
|
-
return modelName.toLowerCase() === entityLower || modelName === entityName;
|
|
432
|
-
}
|
|
433
|
-
return false;
|
|
434
|
-
});
|
|
435
|
-
if (relevantHandler) {
|
|
436
|
-
const parts = relevantHandler.split(':');
|
|
437
|
-
if (parts.length === 3 && parts[1] === 'default') {
|
|
438
|
-
// Format: modelname:default:action -> use action name as method
|
|
439
|
-
return parts[2];
|
|
440
|
-
}
|
|
441
|
-
else if (parts.length === 2) {
|
|
442
|
-
// Format: modelname:custommethod -> use custom method name
|
|
443
|
-
return parts[1];
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
// Fallback to action name
|
|
447
|
-
return action;
|
|
165
|
+
generateGetResourceOwnerMethod(modelName) {
|
|
166
|
+
const storeVar = `${modelName.toLowerCase()}Store`;
|
|
167
|
+
return `
|
|
168
|
+
/**
|
|
169
|
+
* Get the owner ID of a resource by its ID.
|
|
170
|
+
* Used for pre-mutation authorization checks.
|
|
171
|
+
*/
|
|
172
|
+
async getResourceOwner(id: number): Promise<number | null> {
|
|
173
|
+
return await this.${storeVar}.getResourceOwner(id);
|
|
174
|
+
}`;
|
|
448
175
|
}
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
isDefault = true;
|
|
462
|
-
}
|
|
463
|
-
else if (parts.length === 2) {
|
|
464
|
-
// Format: modelname:custommethod
|
|
465
|
-
methodName = parts[1];
|
|
466
|
-
isDefault = false;
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
return '';
|
|
470
|
-
}
|
|
471
|
-
const entityLower = entityName.toLowerCase();
|
|
472
|
-
// Generate method parameters based on whether it's default or custom
|
|
473
|
-
let methodParams;
|
|
474
|
-
let returnType;
|
|
475
|
-
let methodImplementation;
|
|
476
|
-
if (isDefault) {
|
|
477
|
-
// For default handlers, use standard parameters
|
|
478
|
-
methodParams = this.generateMethodParams(actionName, entityName);
|
|
479
|
-
returnType = this.generateReturnType(actionName, entityName);
|
|
480
|
-
// Get the default implementation template
|
|
481
|
-
const template = serviceTemplates_1.serviceTemplates.defaultImplementations[actionName];
|
|
482
|
-
if (template) {
|
|
483
|
-
methodImplementation = template
|
|
484
|
-
.replace(/{{ENTITY_NAME}}/g, entityName)
|
|
485
|
-
.replace(/{{ENTITY_LOWER}}/g, entityLower);
|
|
486
|
-
// Handle constructor args for create action
|
|
487
|
-
if (actionName === 'create') {
|
|
488
|
-
const relationshipLoading = this.generateRelationshipLoading(moduleConfig, entityName);
|
|
489
|
-
const constructorArgs = this.generateConstructorArgs(moduleConfig, entityName);
|
|
490
|
-
if (relationshipLoading) {
|
|
491
|
-
// Insert relationship loading before the entity creation
|
|
492
|
-
methodImplementation = relationshipLoading + '\n ' + methodImplementation;
|
|
493
|
-
}
|
|
494
|
-
methodImplementation = methodImplementation.replace(/{{CONSTRUCTOR_ARGS}}/g, constructorArgs);
|
|
495
|
-
}
|
|
496
|
-
// Handle setter calls for update action
|
|
497
|
-
if (actionName === 'update') {
|
|
498
|
-
const setterCalls = this.generateUpdateSetterCalls(moduleConfig, entityName, true);
|
|
499
|
-
methodImplementation = methodImplementation.replace(/{{UPDATE_SETTER_CALLS}}/g, setterCalls);
|
|
500
|
-
}
|
|
501
|
-
// Special-case: list action with only owner role → fetch by userId
|
|
502
|
-
if (actionName === 'list') {
|
|
503
|
-
const onlyOwner = roles.length > 0 && roles.every(r => r === 'owner');
|
|
504
|
-
if (onlyOwner) {
|
|
505
|
-
methodImplementation = `const ${entityLower}s = await this.${entityLower}Store.getAllByUserId(user.id, page, limit);\n return ${entityLower}s;`;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
176
|
+
generateService(modelName, useCases, aggregateConfig, childInfo) {
|
|
177
|
+
const serviceName = `${modelName}Service`;
|
|
178
|
+
const storeName = `${modelName}Store`;
|
|
179
|
+
const storeVar = `${modelName.toLowerCase()}Store`;
|
|
180
|
+
// Collect all unique handlers
|
|
181
|
+
const handlers = this.collectHandlers(useCases);
|
|
182
|
+
// Generate methods for each handler
|
|
183
|
+
const methods = [];
|
|
184
|
+
handlers.forEach(handler => {
|
|
185
|
+
if (handler.startsWith('default:')) {
|
|
186
|
+
const actionName = handler.replace('default:', '');
|
|
187
|
+
methods.push(this.generateDefaultHandlerMethod(modelName, actionName, aggregateConfig, childInfo));
|
|
508
188
|
}
|
|
509
189
|
else {
|
|
510
|
-
|
|
190
|
+
methods.push(this.generateCustomHandlerMethod(modelName, handler));
|
|
511
191
|
}
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
// For custom handlers, use result and context parameters
|
|
515
|
-
methodParams = 'result: any, context: any';
|
|
516
|
-
returnType = 'any';
|
|
517
|
-
methodImplementation = `// TODO: Implement custom ${methodName} method for ${entityName}`;
|
|
518
|
-
}
|
|
519
|
-
const permissionCheck = isDefault ? this.generatePermissionCheck(actionName, roles, entityName) : '';
|
|
520
|
-
const hasUserParam = roles.length > 0 && !roles.includes('all');
|
|
521
|
-
const variables = {
|
|
522
|
-
METHOD_NAME: methodName,
|
|
523
|
-
METHOD_PARAMS: methodParams,
|
|
524
|
-
USER_PARAM: hasUserParam ? ', user: AuthenticatedUser' : '',
|
|
525
|
-
RETURN_TYPE: returnType,
|
|
526
|
-
PERMISSION_CHECK: permissionCheck,
|
|
527
|
-
METHOD_IMPLEMENTATION: methodImplementation
|
|
528
|
-
};
|
|
529
|
-
return this.replaceTemplateVars(serviceTemplates_1.serviceTemplates.serviceMethod, variables);
|
|
530
|
-
}
|
|
531
|
-
generateServiceForModel(model, moduleName, moduleConfig, hasGlobalPermissions) {
|
|
532
|
-
if (!moduleConfig.actions) {
|
|
533
|
-
return '';
|
|
534
|
-
}
|
|
535
|
-
const entityName = model.name;
|
|
536
|
-
const entityLower = entityName.toLowerCase();
|
|
537
|
-
const actionPermissions = this.getActionPermissions(moduleName, moduleConfig);
|
|
538
|
-
const hasPermissions = !!(hasGlobalPermissions && moduleConfig.permissions && moduleConfig.permissions.length > 0);
|
|
539
|
-
// Collect all unique handlers for this model
|
|
540
|
-
const modelHandlers = new Set();
|
|
541
|
-
Object.entries(moduleConfig.actions || {}).forEach(([action, actionConfig]) => {
|
|
542
|
-
const handlers = actionConfig.handlers || [];
|
|
543
|
-
handlers.forEach(handler => {
|
|
544
|
-
// Only include handlers that apply to this model
|
|
545
|
-
if (handler.startsWith(`${entityLower}:`) || handler.startsWith(`${entityName}:`)) {
|
|
546
|
-
modelHandlers.add(handler);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
});
|
|
550
|
-
// Generate a method for each unique handler
|
|
551
|
-
const serviceMethods = Array.from(modelHandlers)
|
|
552
|
-
.map(handler => {
|
|
553
|
-
// Determine which actions use this handler to get permissions
|
|
554
|
-
const actionsUsingHandler = Object.entries(moduleConfig.actions || {})
|
|
555
|
-
.filter(([, actionConfig]) => (actionConfig.handlers || []).includes(handler))
|
|
556
|
-
.map(([action]) => action);
|
|
557
|
-
// Use permissions from the first action that uses this handler
|
|
558
|
-
const firstAction = actionsUsingHandler[0];
|
|
559
|
-
const roles = firstAction ? (actionPermissions[firstAction] || []) : [];
|
|
560
|
-
return this.generateHandlerMethod(handler, entityName, roles, hasPermissions, moduleConfig);
|
|
561
|
-
})
|
|
562
|
-
.filter(method => method) // Remove empty methods
|
|
563
|
-
.join('\n\n');
|
|
564
|
-
// Add foreign store constructor params
|
|
565
|
-
const foreignStoreParams = this.generateForeignStoreConstructorParams(model);
|
|
566
|
-
const serviceClass = this.replaceTemplateVars(serviceTemplates_1.serviceTemplates.serviceClass, {
|
|
567
|
-
ENTITY_NAME: entityName,
|
|
568
|
-
ENTITY_LOWER: entityLower,
|
|
569
|
-
AUTH_SERVICE_PARAM: foreignStoreParams,
|
|
570
|
-
SERVICE_METHODS: serviceMethods
|
|
571
|
-
});
|
|
572
|
-
const customImports = this.generateCustomImports(moduleConfig);
|
|
573
|
-
const foreignStoreImports = this.generateForeignStoreImports(model);
|
|
574
|
-
return this.replaceTemplateVars(serviceTemplates_1.serviceFileTemplate, {
|
|
575
|
-
ENTITY_NAME: entityName,
|
|
576
|
-
PERMISSIONS_IMPORT: hasPermissions ? "\nimport type { AuthenticatedUser } from '@currentjs/router';" : '',
|
|
577
|
-
CUSTOM_IMPORTS: customImports + foreignStoreImports,
|
|
578
|
-
SERVICE_CLASS: serviceClass
|
|
579
192
|
});
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (!moduleConfig.models || moduleConfig.models.length === 0) {
|
|
584
|
-
return '';
|
|
193
|
+
const listByParentMethod = this.generateListByParentMethod(modelName, childInfo);
|
|
194
|
+
if (listByParentMethod) {
|
|
195
|
+
methods.push(listByParentMethod);
|
|
585
196
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const relationshipFields = model.fields.filter(f => this.isRelationshipField(f));
|
|
590
|
-
if (relationshipFields.length === 0) {
|
|
591
|
-
return '';
|
|
197
|
+
const getResourceOwnerMethod = this.generateGetResourceOwnerMethod(modelName);
|
|
198
|
+
if (getResourceOwnerMethod) {
|
|
199
|
+
methods.push(getResourceOwnerMethod);
|
|
592
200
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
201
|
+
return `import { Injectable } from '../../../../system';
|
|
202
|
+
import { ${modelName} } from '../../domain/entities/${modelName}';
|
|
203
|
+
import { ${storeName} } from '../../infrastructure/stores/${storeName}';
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Service layer for ${modelName}
|
|
207
|
+
* Contains business logic handlers that can be composed in use cases
|
|
208
|
+
*/
|
|
209
|
+
@Injectable()
|
|
210
|
+
export class ${serviceName} {
|
|
211
|
+
constructor(
|
|
212
|
+
private ${storeVar}: ${storeName}
|
|
213
|
+
) {}
|
|
214
|
+
|
|
215
|
+
${methods.join('\n\n')}
|
|
216
|
+
}`;
|
|
598
217
|
}
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
218
|
+
generateFromConfig(config) {
|
|
219
|
+
const result = {};
|
|
220
|
+
// Collect all aggregates
|
|
221
|
+
if (config.domain.aggregates) {
|
|
222
|
+
Object.entries(config.domain.aggregates).forEach(([name, aggConfig]) => {
|
|
223
|
+
this.availableAggregates.set(name, aggConfig);
|
|
224
|
+
});
|
|
603
225
|
}
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
generateCustomImports(moduleConfig) {
|
|
612
|
-
if (!moduleConfig.actions)
|
|
613
|
-
return '';
|
|
614
|
-
const imports = [];
|
|
615
|
-
Object.values(moduleConfig.actions).forEach(actionConfig => {
|
|
616
|
-
if (actionConfig.handlers) {
|
|
617
|
-
actionConfig.handlers.forEach(handler => {
|
|
618
|
-
if (handler.startsWith(constants_1.PATH_PATTERNS.MODULES_DIRECTIVE)) {
|
|
619
|
-
const functionName = this.extractFunctionName(handler);
|
|
620
|
-
const importPath = handler.replace(constants_1.PATH_PATTERNS.MODULES_DIRECTIVE, '../../');
|
|
621
|
-
imports.push(`import ${functionName} from '${importPath}';`);
|
|
622
|
-
}
|
|
623
|
-
});
|
|
226
|
+
const childEntityMap = (0, childEntityUtils_1.buildChildEntityMap)(config);
|
|
227
|
+
// Generate a Service file for each model
|
|
228
|
+
Object.entries(config.useCases).forEach(([modelName, useCases]) => {
|
|
229
|
+
const aggregateConfig = this.availableAggregates.get(modelName);
|
|
230
|
+
if (!aggregateConfig) {
|
|
231
|
+
console.warn(`Warning: No aggregate found for model ${modelName}`);
|
|
232
|
+
return;
|
|
624
233
|
}
|
|
234
|
+
const childInfo = childEntityMap.get(modelName);
|
|
235
|
+
result[modelName] = this.generateService(modelName, useCases, aggregateConfig, childInfo);
|
|
625
236
|
});
|
|
626
|
-
return
|
|
237
|
+
return result;
|
|
627
238
|
}
|
|
628
239
|
generateFromYamlFile(yamlFilePath) {
|
|
629
240
|
const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
630
241
|
const config = (0, yaml_1.parse)(yamlContent);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (config.modules) {
|
|
634
|
-
Object.entries(config.modules).forEach(([moduleName, moduleConfig]) => {
|
|
635
|
-
if (moduleConfig.models && moduleConfig.models.length > 0) {
|
|
636
|
-
// Set available models for relationship detection
|
|
637
|
-
this.setAvailableModels(moduleConfig.models);
|
|
638
|
-
// Generate a service for each model
|
|
639
|
-
moduleConfig.models.forEach(model => {
|
|
640
|
-
const serviceCode = this.generateServiceForModel(model, moduleName, moduleConfig, hasGlobalPermissions);
|
|
641
|
-
if (serviceCode) {
|
|
642
|
-
result[model.name] = serviceCode;
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
});
|
|
242
|
+
if (!(0, configTypes_1.isValidModuleConfig)(config)) {
|
|
243
|
+
throw new Error('Configuration does not match new module format. Expected useCases structure.');
|
|
647
244
|
}
|
|
648
|
-
|
|
649
|
-
const moduleName = 'Module';
|
|
650
|
-
const moduleConfig = config;
|
|
651
|
-
if (moduleConfig.models && moduleConfig.models.length > 0) {
|
|
652
|
-
// Set available models for relationship detection
|
|
653
|
-
this.setAvailableModels(moduleConfig.models);
|
|
654
|
-
// Generate a service for each model
|
|
655
|
-
moduleConfig.models.forEach(model => {
|
|
656
|
-
const serviceCode = this.generateServiceForModel(model, moduleName, moduleConfig, hasGlobalPermissions);
|
|
657
|
-
if (serviceCode) {
|
|
658
|
-
result[model.name] = serviceCode;
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
return result;
|
|
245
|
+
return this.generateFromConfig(config);
|
|
664
246
|
}
|
|
665
|
-
async generateAndSaveFiles(yamlFilePath
|
|
666
|
-
const
|
|
667
|
-
const servicesDir = path.join(
|
|
247
|
+
async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
|
|
248
|
+
const servicesByModel = this.generateFromYamlFile(yamlFilePath);
|
|
249
|
+
const servicesDir = path.join(moduleDir, 'application', 'services');
|
|
668
250
|
fs.mkdirSync(servicesDir, { recursive: true });
|
|
669
|
-
for (const [
|
|
670
|
-
const
|
|
671
|
-
const filePath = path.join(servicesDir, fileName);
|
|
251
|
+
for (const [modelName, code] of Object.entries(servicesByModel)) {
|
|
252
|
+
const filePath = path.join(servicesDir, `${modelName}Service.ts`);
|
|
672
253
|
// eslint-disable-next-line no-await-in-loop
|
|
673
|
-
await (0, generationRegistry_1.writeGeneratedFile)(filePath,
|
|
254
|
+
await (0, generationRegistry_1.writeGeneratedFile)(filePath, code, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
|
|
674
255
|
}
|
|
675
256
|
// eslint-disable-next-line no-console
|
|
676
257
|
console.log('\n' + colors_1.colors.green('Service files generated successfully!') + '\n');
|