@azure-net/cli 5.0.0-3 → 5.0.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/bin/azure-net.js +5 -46
- package/bin/azure-net.ts +5 -5
- package/package.json +1 -1
- package/src/checks/checkDomain.js +8 -38
- package/src/checks/checkFoldersStructure.js +0 -14
- package/src/checks/checkHooks.js +0 -5
- package/src/checks/checkImports.js +0 -1
- package/src/checks/checkInternal.js +0 -1
- package/src/checks/checkLayerBoundaries.js +50 -54
- package/src/checks/checkLayerBoundaries.ts +55 -58
- package/src/checks/checkPresenterNames.js +1 -37
- package/src/checks/checkPresenterNames.ts +1 -6
- package/src/checks/checkProject.js +0 -6
- package/src/checks/checkProviderGraph.js +1 -38
- package/src/checks/checkProviderNames.js +0 -28
- package/src/checks/index.js +0 -1
- package/src/generate/layers/generateRepository.js +4 -19
- package/src/generate/layers/generateRepositoryMethod.js +0 -12
- package/src/generate/layers/generateUseCases.js +0 -6
- package/src/generate/layers/repositoryShared.js +41 -104
- package/src/generate/layers/repositoryShared.ts +17 -12
- package/src/generate/module/generateDomain.js +0 -25
- package/src/generate/module/generateModule.js +0 -10
- package/src/generate/module/generateModulePreset.js +10 -51
- package/src/generate/module/generateModulePreset.ts +9 -8
- package/src/generate/module/generatePresenter.js +29 -76
- package/src/generate/module/generatePresenter.ts +15 -10
- package/src/generate/module/generateRepository.js +0 -17
- package/src/generate/module/generateSchemaFromType.js +9 -58
- package/src/generate/module/generateSchemaFromType.ts +1 -2
- package/src/generate/module/generateTypesJson.js +4 -45
- package/src/generate/module/generateUseCases.js +0 -9
- package/src/generate/module/repositoryModuleShared.js +21 -98
- package/src/generate/module/repositoryModuleShared.ts +14 -10
- package/src/generate/ui/generateComponent.js +0 -16
- package/src/generate/ui/generateDesignComponent.js +0 -12
- package/src/generate/ui/generateWidget.js +0 -12
- package/src/init/initAliases.js +42 -64
- package/src/init/initAliases.ts +47 -42
- package/src/init/initEdges.js +0 -15
- package/src/init/initStructure.js +48 -21
- package/src/init/initStructure.ts +51 -3
- package/src/install/installFresh.js +0 -16
- package/src/plugins/addLint.js +11 -46
- package/src/scaffolds/createCoreSchema.js +6 -40
- package/src/scaffolds/createCoreSchema.ts +6 -18
- package/src/scaffolds/createDatasource.js +26 -61
- package/src/scaffolds/createDatasource.ts +26 -40
- package/src/scaffolds/createDatasourceProvider.js +19 -56
- package/src/scaffolds/createDatasourceProvider.ts +20 -34
- package/src/scaffolds/createPresenterFactory.js +5 -34
- package/src/scaffolds/createPresenterFactory.ts +5 -17
- package/src/scaffolds/createProgram.js +1 -5
- package/src/scaffolds/createResponse.js +3 -11
- package/src/scaffolds/createResponse.ts +3 -1
- package/src/scaffolds/createSchemaRule.js +3 -8
- package/src/scaffolds/createSchemaRule.ts +3 -1
- package/src/scaffolds/createTranslationManager.js +3 -14
- package/src/scaffolds/createTranslationManager.ts +3 -1
- package/src/utils/contextUtils.js +5 -20
- package/src/utils/contextUtils.ts +2 -3
- package/src/utils/fileUtils.js +3 -38
- package/src/utils/fileUtils.ts +0 -26
- package/src/utils/loadConfig.js +1 -14
- package/src/utils/loadConfig.ts +1 -3
- package/src/utils/sharedFoundation.js +48 -0
- package/src/utils/sharedFoundation.ts +70 -0
|
@@ -5,7 +5,7 @@ import { loadUserConfig } from '../../utils/loadConfig.js';
|
|
|
5
5
|
import { normalizeContexts } from '../../init/initAliases.js';
|
|
6
6
|
import { toCamelCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
7
7
|
import { updateIndexTs } from '../../utils/fileUtils.js';
|
|
8
|
-
|
|
8
|
+
import { getFoundationConstructImportPath, getFoundationConstructPath, getSharedState } from '../../utils/sharedFoundation.js';
|
|
9
9
|
const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'];
|
|
10
10
|
const MUTATION_METHODS = new Set(['put', 'patch', 'delete']);
|
|
11
11
|
const RESERVED_IDENTIFIERS = new Set([
|
|
@@ -56,20 +56,16 @@ const RESERVED_IDENTIFIERS = new Set([
|
|
|
56
56
|
'static',
|
|
57
57
|
'yield'
|
|
58
58
|
]);
|
|
59
|
-
|
|
60
59
|
const normalizeAlias = (value, fallback) => {
|
|
61
60
|
const raw = String(value ?? '').trim();
|
|
62
61
|
if (!raw.length) return fallback;
|
|
63
62
|
return raw.startsWith('$') ? raw : `$${raw}`;
|
|
64
63
|
};
|
|
65
|
-
|
|
66
64
|
const toTypeFileName = (typeName) => String(typeName ?? '').replace(/^I/, '') || 'Type';
|
|
67
|
-
|
|
68
65
|
export const ensureRepositoryName = (rawName) => {
|
|
69
66
|
const normalized = toPascalCase(String(rawName ?? '').trim() || 'Repository');
|
|
70
67
|
return normalized.endsWith('Repository') ? normalized : `${normalized}Repository`;
|
|
71
68
|
};
|
|
72
|
-
|
|
73
69
|
export const getRepositoryMeta = (repositoryName) => {
|
|
74
70
|
const className = ensureRepositoryName(repositoryName);
|
|
75
71
|
const baseName = className.replace(/Repository$/, '') || 'Repository';
|
|
@@ -79,7 +75,6 @@ export const getRepositoryMeta = (repositoryName) => {
|
|
|
79
75
|
const repositoryDependencyName = RESERVED_IDENTIFIERS.has(repositoryDependencyNameRaw)
|
|
80
76
|
? `${repositoryDependencyNameRaw}Repository`
|
|
81
77
|
: repositoryDependencyNameRaw;
|
|
82
|
-
|
|
83
78
|
return {
|
|
84
79
|
className,
|
|
85
80
|
baseName,
|
|
@@ -88,21 +83,18 @@ export const getRepositoryMeta = (repositoryName) => {
|
|
|
88
83
|
repositoryDependencyName
|
|
89
84
|
};
|
|
90
85
|
};
|
|
91
|
-
|
|
92
86
|
export const getConfigState = async () => {
|
|
93
87
|
const config = await loadUserConfig();
|
|
94
88
|
const contexts = normalizeContexts(config.contexts);
|
|
95
|
-
const
|
|
96
|
-
return { contexts,
|
|
89
|
+
const { sharedAlias, sharedContext } = await getSharedState();
|
|
90
|
+
return { contexts, sharedAlias, sharedContextName: sharedContext.name };
|
|
97
91
|
};
|
|
98
|
-
|
|
99
92
|
export const selectContext = async (message = 'Select context:') => {
|
|
100
93
|
const { contexts } = await getConfigState();
|
|
101
94
|
if (!contexts.length) {
|
|
102
95
|
console.error('❌ Cannot continue: `contexts` is missing or empty in azure-net.config.ts/js');
|
|
103
96
|
return null;
|
|
104
97
|
}
|
|
105
|
-
|
|
106
98
|
const { context } = await prompts({
|
|
107
99
|
type: 'select',
|
|
108
100
|
name: 'context',
|
|
@@ -110,30 +102,25 @@ export const selectContext = async (message = 'Select context:') => {
|
|
|
110
102
|
choices: contexts.map((item) => ({ title: item.name, value: item.name })),
|
|
111
103
|
initial: 0
|
|
112
104
|
});
|
|
113
|
-
|
|
114
105
|
return context ? String(context) : null;
|
|
115
106
|
};
|
|
116
|
-
|
|
117
107
|
export const resolveContextAlias = (contexts, contextName) => {
|
|
118
108
|
const found = contexts.find((item) => item.name === contextName);
|
|
119
109
|
if (!found) return `$${contextName}`;
|
|
120
110
|
return normalizeAlias(found.alias, `$${contextName}`);
|
|
121
111
|
};
|
|
122
|
-
|
|
123
112
|
export const resolveRepositoriesPath = (contextName) =>
|
|
124
113
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'infrastructure', 'http', 'repositories');
|
|
125
|
-
|
|
126
114
|
export const resolveDatasourcesPath = (contextName) =>
|
|
127
115
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'infrastructure', 'http', 'datasources');
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
116
|
+
export const resolveSharedDatasourcesPath = async () => {
|
|
117
|
+
const { sharedContext } = await getSharedState();
|
|
118
|
+
return getFoundationConstructPath(sharedContext.name, 'datasource');
|
|
119
|
+
};
|
|
131
120
|
export const resolveDomainRootPath = (contextName, domainName) =>
|
|
132
121
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'domain', domainName);
|
|
133
|
-
|
|
134
122
|
export const resolveUseCasesPath = (contextName) =>
|
|
135
123
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'application', 'use-cases');
|
|
136
|
-
|
|
137
124
|
export const getAvailableTsNames = async (targetPath) => {
|
|
138
125
|
try {
|
|
139
126
|
const files = await fs.readdir(targetPath);
|
|
@@ -142,33 +129,28 @@ export const getAvailableTsNames = async (targetPath) => {
|
|
|
142
129
|
return [];
|
|
143
130
|
}
|
|
144
131
|
};
|
|
145
|
-
|
|
146
132
|
export const selectDatasource = async (contextName) => {
|
|
147
|
-
const { contexts,
|
|
133
|
+
const { contexts, sharedAlias } = await getConfigState();
|
|
148
134
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
149
|
-
const
|
|
135
|
+
const sharedDatasources = await getAvailableTsNames(await resolveSharedDatasourcesPath());
|
|
150
136
|
const contextDatasources = await getAvailableTsNames(resolveDatasourcesPath(contextName));
|
|
151
|
-
|
|
152
137
|
const choices = [];
|
|
153
|
-
for (const datasource of
|
|
138
|
+
for (const datasource of sharedDatasources) {
|
|
154
139
|
choices.push({
|
|
155
|
-
title: `${datasource} (
|
|
156
|
-
value: { name: datasource, importPath:
|
|
140
|
+
title: `${datasource} (shared foundation)`,
|
|
141
|
+
value: { name: datasource, importPath: getFoundationConstructImportPath(sharedAlias, 'datasource') }
|
|
157
142
|
});
|
|
158
143
|
}
|
|
159
|
-
|
|
160
144
|
for (const datasource of contextDatasources) {
|
|
161
145
|
choices.push({
|
|
162
146
|
title: `${datasource} (${contextName})`,
|
|
163
147
|
value: { name: datasource, importPath: `${contextAlias}/layers/infrastructure/http/datasources` }
|
|
164
148
|
});
|
|
165
149
|
}
|
|
166
|
-
|
|
167
150
|
if (!choices.length) {
|
|
168
|
-
console.error(`❌ No datasources found in
|
|
151
|
+
console.error(`❌ No datasources found in shared foundation or context "${contextName}".`);
|
|
169
152
|
return null;
|
|
170
153
|
}
|
|
171
|
-
|
|
172
154
|
const { datasource } = await prompts({
|
|
173
155
|
type: 'select',
|
|
174
156
|
name: 'datasource',
|
|
@@ -176,10 +158,8 @@ export const selectDatasource = async (contextName) => {
|
|
|
176
158
|
choices,
|
|
177
159
|
initial: 0
|
|
178
160
|
});
|
|
179
|
-
|
|
180
161
|
return datasource ?? null;
|
|
181
162
|
};
|
|
182
|
-
|
|
183
163
|
export const selectDomain = async (contextName) => {
|
|
184
164
|
const domainPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'domain');
|
|
185
165
|
try {
|
|
@@ -189,7 +169,6 @@ export const selectDomain = async (contextName) => {
|
|
|
189
169
|
console.error(`❌ No domain folders found in ${domainPath}`);
|
|
190
170
|
return null;
|
|
191
171
|
}
|
|
192
|
-
|
|
193
172
|
const { domain } = await prompts({
|
|
194
173
|
type: 'select',
|
|
195
174
|
name: 'domain',
|
|
@@ -197,19 +176,16 @@ export const selectDomain = async (contextName) => {
|
|
|
197
176
|
choices: domains.map((item) => ({ title: item, value: item })),
|
|
198
177
|
initial: 0
|
|
199
178
|
});
|
|
200
|
-
|
|
201
179
|
return domain ? String(domain) : null;
|
|
202
180
|
} catch {
|
|
203
181
|
console.error(`❌ Domain path not found: ${domainPath}`);
|
|
204
182
|
return null;
|
|
205
183
|
}
|
|
206
184
|
};
|
|
207
|
-
|
|
208
185
|
const extractInterfaceName = (content, fallback) => {
|
|
209
186
|
const match = content.match(/export\s+interface\s+([A-Za-z0-9_]+)/);
|
|
210
187
|
return match?.[1] ?? fallback;
|
|
211
188
|
};
|
|
212
|
-
|
|
213
189
|
export const getDomainTypes = async (contextName, domainName) => {
|
|
214
190
|
const root = resolveDomainRootPath(contextName, domainName);
|
|
215
191
|
const result = [];
|
|
@@ -220,7 +196,6 @@ export const getDomainTypes = async (contextName, domainName) => {
|
|
|
220
196
|
for (const fileName of files) {
|
|
221
197
|
if (!fileName.endsWith('.ts') || fileName === 'index.ts') continue;
|
|
222
198
|
const absolute = path.join(layerPath, fileName);
|
|
223
|
-
// eslint-disable-next-line no-await-in-loop
|
|
224
199
|
const content = await fs.readFile(absolute, 'utf-8');
|
|
225
200
|
const fallback = fileName.replace(/\.ts$/, '');
|
|
226
201
|
const interfaceName = extractInterfaceName(content, fallback);
|
|
@@ -230,15 +205,12 @@ export const getDomainTypes = async (contextName, domainName) => {
|
|
|
230
205
|
// ignore
|
|
231
206
|
}
|
|
232
207
|
};
|
|
233
|
-
|
|
234
208
|
await readLayer('ports');
|
|
235
209
|
await readLayer('model');
|
|
236
210
|
return result;
|
|
237
211
|
};
|
|
238
|
-
|
|
239
212
|
const isPrimitiveType = (value) =>
|
|
240
213
|
['string', 'number', 'boolean', 'unknown', 'void', 'null', 'undefined', 'bigint', 'symbol', 'never'].includes(String(value ?? '').trim());
|
|
241
|
-
|
|
242
214
|
const buildResponseSelection = async (domainTypes) => {
|
|
243
215
|
const { isPrimitive } = await prompts({
|
|
244
216
|
type: 'confirm',
|
|
@@ -246,7 +218,6 @@ const buildResponseSelection = async (domainTypes) => {
|
|
|
246
218
|
message: 'Is response type primitive?',
|
|
247
219
|
initial: false
|
|
248
220
|
});
|
|
249
|
-
|
|
250
221
|
if (isPrimitive) {
|
|
251
222
|
const { primitiveType } = await prompts({
|
|
252
223
|
type: 'text',
|
|
@@ -254,15 +225,12 @@ const buildResponseSelection = async (domainTypes) => {
|
|
|
254
225
|
message: 'Primitive response type:',
|
|
255
226
|
validate: (value) => (String(value ?? '').trim().length > 0 ? true : 'Type is required')
|
|
256
227
|
});
|
|
257
|
-
|
|
258
228
|
return { kind: 'primitive', type: String(primitiveType).trim(), layer: null };
|
|
259
229
|
}
|
|
260
|
-
|
|
261
230
|
if (!domainTypes.length) {
|
|
262
231
|
console.error('❌ Domain has no model/ports types to choose from.');
|
|
263
232
|
return null;
|
|
264
233
|
}
|
|
265
|
-
|
|
266
234
|
const { selectedType } = await prompts({
|
|
267
235
|
type: 'select',
|
|
268
236
|
name: 'selectedType',
|
|
@@ -273,26 +241,21 @@ const buildResponseSelection = async (domainTypes) => {
|
|
|
273
241
|
})),
|
|
274
242
|
initial: 0
|
|
275
243
|
});
|
|
276
|
-
|
|
277
244
|
if (!selectedType) return null;
|
|
278
245
|
return { kind: 'domain', type: selectedType.name, layer: selectedType.layer };
|
|
279
246
|
};
|
|
280
|
-
|
|
281
247
|
const buildDomainRequestTypeSelection = async (question, domainTypes) => {
|
|
282
248
|
if (!domainTypes.length) {
|
|
283
249
|
console.error('❌ Domain has no model/ports types to choose from.');
|
|
284
250
|
return null;
|
|
285
251
|
}
|
|
286
|
-
|
|
287
252
|
const { enabled } = await prompts({
|
|
288
253
|
type: 'confirm',
|
|
289
254
|
name: 'enabled',
|
|
290
255
|
message: question,
|
|
291
256
|
initial: false
|
|
292
257
|
});
|
|
293
|
-
|
|
294
258
|
if (!enabled) return null;
|
|
295
|
-
|
|
296
259
|
const { selectedType } = await prompts({
|
|
297
260
|
type: 'select',
|
|
298
261
|
name: 'selectedType',
|
|
@@ -303,10 +266,8 @@ const buildDomainRequestTypeSelection = async (question, domainTypes) => {
|
|
|
303
266
|
})),
|
|
304
267
|
initial: 0
|
|
305
268
|
});
|
|
306
|
-
|
|
307
269
|
return selectedType ?? null;
|
|
308
270
|
};
|
|
309
|
-
|
|
310
271
|
const buildPathParamTypeSelection = async () => {
|
|
311
272
|
const { enabled } = await prompts({
|
|
312
273
|
type: 'confirm',
|
|
@@ -314,9 +275,7 @@ const buildPathParamTypeSelection = async () => {
|
|
|
314
275
|
message: 'Has path param?',
|
|
315
276
|
initial: false
|
|
316
277
|
});
|
|
317
|
-
|
|
318
278
|
if (!enabled) return null;
|
|
319
|
-
|
|
320
279
|
const { type } = await prompts({
|
|
321
280
|
type: 'select',
|
|
322
281
|
name: 'type',
|
|
@@ -327,10 +286,8 @@ const buildPathParamTypeSelection = async () => {
|
|
|
327
286
|
],
|
|
328
287
|
initial: 0
|
|
329
288
|
});
|
|
330
|
-
|
|
331
289
|
return type ?? 'string';
|
|
332
290
|
};
|
|
333
|
-
|
|
334
291
|
export const promptMethodDefinition = async (domainTypes) => {
|
|
335
292
|
const { methodNameRaw } = await prompts({
|
|
336
293
|
type: 'text',
|
|
@@ -338,7 +295,6 @@ export const promptMethodDefinition = async (domainTypes) => {
|
|
|
338
295
|
message: 'Method name:',
|
|
339
296
|
validate: (value) => (String(value ?? '').trim().length > 0 ? true : 'Method name is required')
|
|
340
297
|
});
|
|
341
|
-
|
|
342
298
|
const { httpMethod } = await prompts({
|
|
343
299
|
type: 'select',
|
|
344
300
|
name: 'httpMethod',
|
|
@@ -346,7 +302,6 @@ export const promptMethodDefinition = async (domainTypes) => {
|
|
|
346
302
|
choices: HTTP_METHODS.map((item) => ({ title: item, value: item })),
|
|
347
303
|
initial: 0
|
|
348
304
|
});
|
|
349
|
-
|
|
350
305
|
const { routeRaw } = await prompts({
|
|
351
306
|
type: 'text',
|
|
352
307
|
name: 'routeRaw',
|
|
@@ -358,15 +313,12 @@ export const promptMethodDefinition = async (domainTypes) => {
|
|
|
358
313
|
return raw.startsWith('/') ? true : 'Path must start with "/"';
|
|
359
314
|
}
|
|
360
315
|
});
|
|
361
|
-
|
|
362
316
|
const response = await buildResponseSelection(domainTypes);
|
|
363
317
|
if (!response) return null;
|
|
364
|
-
|
|
365
318
|
const normalizedMethod = String(httpMethod ?? 'get').toLowerCase();
|
|
366
319
|
let queryType = null;
|
|
367
320
|
let bodyType = null;
|
|
368
321
|
let pathParamType = null;
|
|
369
|
-
|
|
370
322
|
if (normalizedMethod === 'get') {
|
|
371
323
|
queryType = await buildDomainRequestTypeSelection('Has query params?', domainTypes);
|
|
372
324
|
} else if (normalizedMethod === 'post') {
|
|
@@ -375,7 +327,6 @@ export const promptMethodDefinition = async (domainTypes) => {
|
|
|
375
327
|
pathParamType = await buildPathParamTypeSelection();
|
|
376
328
|
bodyType = await buildDomainRequestTypeSelection('Has request body?', domainTypes);
|
|
377
329
|
}
|
|
378
|
-
|
|
379
330
|
return {
|
|
380
331
|
name: toCamelCase(String(methodNameRaw ?? '').trim() || 'method'),
|
|
381
332
|
httpMethod: normalizedMethod,
|
|
@@ -386,9 +337,7 @@ export const promptMethodDefinition = async (domainTypes) => {
|
|
|
386
337
|
pathParamType
|
|
387
338
|
};
|
|
388
339
|
};
|
|
389
|
-
|
|
390
340
|
const getResponseTypeName = (method) => method.response.type;
|
|
391
|
-
|
|
392
341
|
const getMethodParams = (method) => {
|
|
393
342
|
const params = [];
|
|
394
343
|
if (method.pathParamType) params.push(`pathParam: ${method.pathParamType}`);
|
|
@@ -396,10 +345,8 @@ const getMethodParams = (method) => {
|
|
|
396
345
|
if (method.bodyType) params.push(`request: ${method.bodyType.name}`);
|
|
397
346
|
return params.join(', ');
|
|
398
347
|
};
|
|
399
|
-
|
|
400
348
|
const getRequestMapping = (method) => {
|
|
401
349
|
const routeWithPath = method.pathParamType ? `${method.route}/\${pathParam}` : method.route;
|
|
402
|
-
|
|
403
350
|
if (method.httpMethod === 'get') {
|
|
404
351
|
if (method.queryType) {
|
|
405
352
|
return {
|
|
@@ -407,13 +354,11 @@ const getRequestMapping = (method) => {
|
|
|
407
354
|
call: `http.get(\`${routeWithPath}\`, { searchParams: query.toSearchParams(request) })`
|
|
408
355
|
};
|
|
409
356
|
}
|
|
410
|
-
|
|
411
357
|
return {
|
|
412
358
|
callbackParams: '{ http }',
|
|
413
359
|
call: `http.get(\`${routeWithPath}\`)`
|
|
414
360
|
};
|
|
415
361
|
}
|
|
416
|
-
|
|
417
362
|
if (method.httpMethod === 'post') {
|
|
418
363
|
if (method.bodyType) {
|
|
419
364
|
return {
|
|
@@ -421,13 +366,11 @@ const getRequestMapping = (method) => {
|
|
|
421
366
|
call: `http.post(\`${routeWithPath}\`, { json: request })`
|
|
422
367
|
};
|
|
423
368
|
}
|
|
424
|
-
|
|
425
369
|
return {
|
|
426
370
|
callbackParams: '{ http }',
|
|
427
371
|
call: `http.post(\`${routeWithPath}\`)`
|
|
428
372
|
};
|
|
429
373
|
}
|
|
430
|
-
|
|
431
374
|
if (method.httpMethod === 'put' || method.httpMethod === 'patch' || method.httpMethod === 'delete') {
|
|
432
375
|
const callMethod = method.httpMethod;
|
|
433
376
|
if (method.bodyType) {
|
|
@@ -436,27 +379,22 @@ const getRequestMapping = (method) => {
|
|
|
436
379
|
call: `http.${callMethod}(\`${routeWithPath}\`, { json: request })`
|
|
437
380
|
};
|
|
438
381
|
}
|
|
439
|
-
|
|
440
382
|
return {
|
|
441
383
|
callbackParams: '{ http }',
|
|
442
384
|
call: `http.${callMethod}(\`${routeWithPath}\`)`
|
|
443
385
|
};
|
|
444
386
|
}
|
|
445
|
-
|
|
446
387
|
return {
|
|
447
388
|
callbackParams: '{ http }',
|
|
448
389
|
call: `http.${method.httpMethod}(\`${routeWithPath}\`)`
|
|
449
390
|
};
|
|
450
391
|
};
|
|
451
|
-
|
|
452
392
|
export const buildRepositoryMethodCode = (method) => {
|
|
453
393
|
const params = getMethodParams(method);
|
|
454
394
|
const responseType = getResponseTypeName(method);
|
|
455
395
|
const mapping = getRequestMapping(method);
|
|
456
|
-
|
|
457
396
|
return `\tpublic async ${method.name}(${params}) {\n\t\treturn this.datasource\n\t\t\t.createRequest<${responseType}>((${mapping.callbackParams}) => ${mapping.call})\n\t\t\t.then((res) => res.getData());\n\t}`;
|
|
458
397
|
};
|
|
459
|
-
|
|
460
398
|
export const buildRepositoryContent = ({ meta, contextAlias, domainName, datasource, methods }) => {
|
|
461
399
|
const typeImports = new Set([meta.repositoryInterfaceName]);
|
|
462
400
|
for (const method of methods) {
|
|
@@ -464,12 +402,10 @@ export const buildRepositoryContent = ({ meta, contextAlias, domainName, datasou
|
|
|
464
402
|
if (method.queryType) typeImports.add(method.queryType.name);
|
|
465
403
|
if (method.bodyType) typeImports.add(method.bodyType.name);
|
|
466
404
|
}
|
|
467
|
-
|
|
468
405
|
const methodsCode = methods.map((method) => buildRepositoryMethodCode(method)).join('\n\n');
|
|
469
406
|
const typeImportLine = typeImports.size
|
|
470
407
|
? `import type { ${Array.from(typeImports).join(', ')} } from '${contextAlias}/layers/domain/${domainName}';\n`
|
|
471
408
|
: '';
|
|
472
|
-
|
|
473
409
|
return `import { ${datasource.name} } from '${datasource.importPath}';
|
|
474
410
|
${typeImportLine}
|
|
475
411
|
export class ${meta.className} implements ${meta.repositoryInterfaceName} {
|
|
@@ -479,15 +415,12 @@ ${methodsCode ? `${methodsCode}\n` : ''}
|
|
|
479
415
|
}
|
|
480
416
|
`;
|
|
481
417
|
};
|
|
482
|
-
|
|
483
418
|
export const writeRepositoryInterface = async ({ contextName, domainName, meta, methods }) => {
|
|
484
419
|
const domainPath = resolveDomainRootPath(contextName, domainName);
|
|
485
420
|
const portsPath = path.join(domainPath, 'ports');
|
|
486
421
|
await fs.mkdir(portsPath, { recursive: true });
|
|
487
|
-
|
|
488
422
|
const portsImports = new Set();
|
|
489
423
|
const modelImports = new Set();
|
|
490
|
-
|
|
491
424
|
for (const method of methods) {
|
|
492
425
|
const allTypes = [];
|
|
493
426
|
if (!isPrimitiveType(method.response.type) && method.response.layer) {
|
|
@@ -495,13 +428,11 @@ export const writeRepositoryInterface = async ({ contextName, domainName, meta,
|
|
|
495
428
|
}
|
|
496
429
|
if (method.queryType) allTypes.push(method.queryType);
|
|
497
430
|
if (method.bodyType) allTypes.push(method.bodyType);
|
|
498
|
-
|
|
499
431
|
for (const typeEntry of allTypes) {
|
|
500
432
|
if (typeEntry.layer === 'ports') portsImports.add(typeEntry.name);
|
|
501
433
|
if (typeEntry.layer === 'model') modelImports.add(typeEntry.name);
|
|
502
434
|
}
|
|
503
435
|
}
|
|
504
|
-
|
|
505
436
|
const imports = [];
|
|
506
437
|
for (const name of Array.from(portsImports)) {
|
|
507
438
|
imports.push(`import type { ${name} } from './${toTypeFileName(name)}';`);
|
|
@@ -509,7 +440,6 @@ export const writeRepositoryInterface = async ({ contextName, domainName, meta,
|
|
|
509
440
|
for (const name of Array.from(modelImports)) {
|
|
510
441
|
imports.push(`import type { ${name} } from '../model/${toTypeFileName(name)}';`);
|
|
511
442
|
}
|
|
512
|
-
|
|
513
443
|
const signatures = methods
|
|
514
444
|
.map((method) => {
|
|
515
445
|
const responseType = getResponseTypeName(method);
|
|
@@ -517,33 +447,33 @@ export const writeRepositoryInterface = async ({ contextName, domainName, meta,
|
|
|
517
447
|
return `\t${method.name}(${params}): Promise<${responseType}>;`;
|
|
518
448
|
})
|
|
519
449
|
.join('\n');
|
|
520
|
-
|
|
521
450
|
const content = `${imports.join('\n')}${imports.length ? '\n\n' : ''}export interface ${meta.repositoryInterfaceName} {\n${signatures ? `${signatures}\n` : ''}}\n`;
|
|
522
|
-
await fs.writeFile(path.join(portsPath, `${meta.repositoryInterfaceName}.ts`), content, 'utf-8');
|
|
451
|
+
await fs.writeFile(path.join(portsPath, `${toTypeFileName(meta.repositoryInterfaceName)}.ts`), content, 'utf-8');
|
|
523
452
|
await updateIndexTs(portsPath);
|
|
524
453
|
};
|
|
525
|
-
|
|
526
454
|
const parseRepositoryFileForDomain = (content, contextAlias) => {
|
|
527
455
|
const escapedAlias = contextAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
528
456
|
const regex = new RegExp(`from\\s+['"]${escapedAlias}/layers/domain/([^'"]+)['"]`);
|
|
529
457
|
const match = content.match(regex);
|
|
530
458
|
return match?.[1] ?? null;
|
|
531
459
|
};
|
|
532
|
-
|
|
533
460
|
const parseRepositoryMethodsFromInterface = (content) => {
|
|
534
461
|
const methods = [];
|
|
535
462
|
const regex = /(\w+)\(([^)]*)\)\s*:\s*Promise<([^>]+)>;/g;
|
|
536
463
|
let match;
|
|
537
464
|
while ((match = regex.exec(content)) !== null) {
|
|
538
|
-
|
|
465
|
+
const methodName = match[1];
|
|
466
|
+
if (methodName) methods.push(methodName);
|
|
539
467
|
}
|
|
540
468
|
return methods;
|
|
541
469
|
};
|
|
542
|
-
|
|
543
470
|
export const createUseCasesForRepository = async ({ contextName, contextAlias, domainName, repositoryName }) => {
|
|
544
471
|
const meta = getRepositoryMeta(repositoryName);
|
|
545
|
-
const interfacePath = path.join(
|
|
546
|
-
|
|
472
|
+
const interfacePath = path.join(
|
|
473
|
+
resolveDomainRootPath(contextName, domainName),
|
|
474
|
+
'ports',
|
|
475
|
+
`${toTypeFileName(meta.repositoryInterfaceName)}.ts`
|
|
476
|
+
);
|
|
547
477
|
let interfaceContent = '';
|
|
548
478
|
try {
|
|
549
479
|
interfaceContent = await fs.readFile(interfacePath, 'utf-8');
|
|
@@ -551,13 +481,10 @@ export const createUseCasesForRepository = async ({ contextName, contextAlias, d
|
|
|
551
481
|
console.error(`❌ Repository interface not found: ${interfacePath}`);
|
|
552
482
|
return false;
|
|
553
483
|
}
|
|
554
|
-
|
|
555
484
|
const methods = parseRepositoryMethodsFromInterface(interfaceContent);
|
|
556
485
|
const useCasesPath = resolveUseCasesPath(contextName);
|
|
557
486
|
await fs.mkdir(useCasesPath, { recursive: true });
|
|
558
|
-
|
|
559
487
|
const declares = methods.map((methodName) => `\tdeclare ${methodName}: ${meta.repositoryInterfaceName}['${methodName}'];`).join('\n');
|
|
560
|
-
|
|
561
488
|
const content = `import { ClassMirror } from '@azure-net/kit';
|
|
562
489
|
import type { ${meta.repositoryInterfaceName} } from '${contextAlias}/layers/domain/${domainName}';
|
|
563
490
|
|
|
@@ -569,12 +496,10 @@ export class ${meta.useCasesClassName} extends ClassMirror {
|
|
|
569
496
|
${declares ? `${declares}\n` : ''}
|
|
570
497
|
}
|
|
571
498
|
`;
|
|
572
|
-
|
|
573
499
|
await fs.writeFile(path.join(useCasesPath, `${meta.useCasesClassName}.ts`), content, 'utf-8');
|
|
574
500
|
await updateIndexTs(useCasesPath);
|
|
575
501
|
return true;
|
|
576
502
|
};
|
|
577
|
-
|
|
578
503
|
export const resolveDomainForRepository = async ({ contextName, contextAlias, repositoryName }) => {
|
|
579
504
|
const repositoryPath = path.join(resolveRepositoriesPath(contextName), `${ensureRepositoryName(repositoryName)}.ts`);
|
|
580
505
|
try {
|
|
@@ -584,12 +509,10 @@ export const resolveDomainForRepository = async ({ contextName, contextAlias, re
|
|
|
584
509
|
return null;
|
|
585
510
|
}
|
|
586
511
|
};
|
|
587
|
-
|
|
588
512
|
export const promptMethodsBatch = async (domainTypes, count) => {
|
|
589
513
|
const methods = [];
|
|
590
514
|
for (let index = 0; index < count; index += 1) {
|
|
591
515
|
console.log(`\n🧩 Method ${index + 1} of ${count}`);
|
|
592
|
-
// eslint-disable-next-line no-await-in-loop
|
|
593
516
|
const method = await promptMethodDefinition(domainTypes);
|
|
594
517
|
if (!method) return null;
|
|
595
518
|
methods.push(method);
|
|
@@ -5,6 +5,7 @@ import { loadUserConfig } from '../../utils/loadConfig.js';
|
|
|
5
5
|
import { normalizeContexts } from '../../init/initAliases.js';
|
|
6
6
|
import { toCamelCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
7
7
|
import { updateIndexTs } from '../../utils/fileUtils.js';
|
|
8
|
+
import { getFoundationConstructImportPath, getFoundationConstructPath, getSharedState } from '../../utils/sharedFoundation.js';
|
|
8
9
|
|
|
9
10
|
type ContextConfig = { name: string; alias: string };
|
|
10
11
|
type TypeLayer = 'ports' | 'model';
|
|
@@ -113,11 +114,11 @@ export const getRepositoryMeta = (repositoryName: unknown): RepositoryMeta => {
|
|
|
113
114
|
};
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
export const getConfigState = async (): Promise<{ contexts: ContextConfig[];
|
|
117
|
+
export const getConfigState = async (): Promise<{ contexts: ContextConfig[]; sharedAlias: string; sharedContextName: string }> => {
|
|
117
118
|
const config = await loadUserConfig();
|
|
118
119
|
const contexts = normalizeContexts(config.contexts);
|
|
119
|
-
const
|
|
120
|
-
return { contexts,
|
|
120
|
+
const { sharedAlias, sharedContext } = await getSharedState();
|
|
121
|
+
return { contexts, sharedAlias, sharedContextName: sharedContext.name };
|
|
121
122
|
};
|
|
122
123
|
|
|
123
124
|
export const selectContext = async (message = 'Select context:'): Promise<string | null> => {
|
|
@@ -150,7 +151,10 @@ export const resolveRepositoriesPath = (contextName: string): string =>
|
|
|
150
151
|
export const resolveDatasourcesPath = (contextName: string): string =>
|
|
151
152
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'infrastructure', 'http', 'datasources');
|
|
152
153
|
|
|
153
|
-
export const
|
|
154
|
+
export const resolveSharedDatasourcesPath = async (): Promise<string> => {
|
|
155
|
+
const { sharedContext } = await getSharedState();
|
|
156
|
+
return getFoundationConstructPath(sharedContext.name, 'datasource');
|
|
157
|
+
};
|
|
154
158
|
|
|
155
159
|
export const resolveDomainRootPath = (contextName: string, domainName: string): string =>
|
|
156
160
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'domain', domainName);
|
|
@@ -168,16 +172,16 @@ export const getAvailableTsNames = async (targetPath: string): Promise<string[]>
|
|
|
168
172
|
};
|
|
169
173
|
|
|
170
174
|
export const selectDatasource = async (contextName: string): Promise<DatasourceInfo | null> => {
|
|
171
|
-
const { contexts,
|
|
175
|
+
const { contexts, sharedAlias } = await getConfigState();
|
|
172
176
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
173
|
-
const
|
|
177
|
+
const sharedDatasources = await getAvailableTsNames(await resolveSharedDatasourcesPath());
|
|
174
178
|
const contextDatasources = await getAvailableTsNames(resolveDatasourcesPath(contextName));
|
|
175
179
|
|
|
176
180
|
const choices: Array<{ title: string; value: DatasourceInfo }> = [];
|
|
177
|
-
for (const datasource of
|
|
181
|
+
for (const datasource of sharedDatasources) {
|
|
178
182
|
choices.push({
|
|
179
|
-
title: `${datasource} (
|
|
180
|
-
value: { name: datasource, importPath:
|
|
183
|
+
title: `${datasource} (shared foundation)`,
|
|
184
|
+
value: { name: datasource, importPath: getFoundationConstructImportPath(sharedAlias, 'datasource') }
|
|
181
185
|
});
|
|
182
186
|
}
|
|
183
187
|
|
|
@@ -189,7 +193,7 @@ export const selectDatasource = async (contextName: string): Promise<DatasourceI
|
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
if (!choices.length) {
|
|
192
|
-
console.error(`❌ No datasources found in
|
|
196
|
+
console.error(`❌ No datasources found in shared foundation or context "${contextName}".`);
|
|
193
197
|
return null;
|
|
194
198
|
}
|
|
195
199
|
|
|
@@ -5,7 +5,6 @@ import { toPascalCase } from '../../utils/contextUtils.js';
|
|
|
5
5
|
import { normalizeContexts } from '../../init/initAliases.js';
|
|
6
6
|
import { loadUserConfig } from '../../utils/loadConfig.js';
|
|
7
7
|
import { writeIfNotExists } from '../../utils/fileUtils.js';
|
|
8
|
-
|
|
9
8
|
const createComponentContent = (propsName) => `<script lang="ts">
|
|
10
9
|
import type { ${propsName} } from './types';
|
|
11
10
|
const {}: ${propsName} = $props();
|
|
@@ -15,22 +14,17 @@ const {}: ${propsName} = $props();
|
|
|
15
14
|
\t@use './style';
|
|
16
15
|
</style>
|
|
17
16
|
`;
|
|
18
|
-
|
|
19
17
|
const createTypesContent = (propsName) => `export interface ${propsName} {}
|
|
20
18
|
`;
|
|
21
|
-
|
|
22
19
|
const createIndexContent = (name) => `export { default as ${name} } from './Component.svelte';
|
|
23
20
|
`;
|
|
24
|
-
|
|
25
21
|
const printContextsError = () => {
|
|
26
22
|
console.error('❌ Cannot generate UI artifact: `contexts` is missing or empty in azure-net.config.ts/js');
|
|
27
23
|
};
|
|
28
|
-
|
|
29
24
|
const selectContext = async () => {
|
|
30
25
|
const config = await loadUserConfig();
|
|
31
26
|
const contexts = normalizeContexts(config.contexts);
|
|
32
27
|
if (!contexts.length) return null;
|
|
33
|
-
|
|
34
28
|
const { context } = await prompts({
|
|
35
29
|
type: 'select',
|
|
36
30
|
name: 'context',
|
|
@@ -38,10 +32,8 @@ const selectContext = async () => {
|
|
|
38
32
|
choices: contexts.map((item) => ({ title: item.name, value: item.name })),
|
|
39
33
|
initial: 0
|
|
40
34
|
});
|
|
41
|
-
|
|
42
35
|
return context ? String(context) : null;
|
|
43
36
|
};
|
|
44
|
-
|
|
45
37
|
export default async function generateComponent() {
|
|
46
38
|
const context = await selectContext();
|
|
47
39
|
if (!context) {
|
|
@@ -49,14 +41,12 @@ export default async function generateComponent() {
|
|
|
49
41
|
process.exitCode = 1;
|
|
50
42
|
return;
|
|
51
43
|
}
|
|
52
|
-
|
|
53
44
|
const { rawName } = await prompts({
|
|
54
45
|
type: 'text',
|
|
55
46
|
name: 'rawName',
|
|
56
47
|
message: 'Component name:',
|
|
57
48
|
validate: (value) => (String(value).trim().length > 0 ? true : 'Name is required')
|
|
58
49
|
});
|
|
59
|
-
|
|
60
50
|
const { componentKind } = await prompts({
|
|
61
51
|
type: 'select',
|
|
62
52
|
name: 'componentKind',
|
|
@@ -68,12 +58,10 @@ export default async function generateComponent() {
|
|
|
68
58
|
],
|
|
69
59
|
initial: 0
|
|
70
60
|
});
|
|
71
|
-
|
|
72
61
|
const name = toPascalCase(String(rawName ?? 'Component')) || 'Component';
|
|
73
62
|
const propsName = `IComponent${name}Props`;
|
|
74
63
|
const kind = componentKind ?? 'shared';
|
|
75
64
|
let targetDir = path.join(process.cwd(), 'src', 'app', context, 'ui', 'components', kind);
|
|
76
|
-
|
|
77
65
|
if (kind === 'page') {
|
|
78
66
|
const { pageFolderRaw } = await prompts({
|
|
79
67
|
type: 'text',
|
|
@@ -81,18 +69,14 @@ export default async function generateComponent() {
|
|
|
81
69
|
message: 'Page folder name:',
|
|
82
70
|
validate: (value) => (String(value).trim().length > 0 ? true : 'Page folder name is required')
|
|
83
71
|
});
|
|
84
|
-
|
|
85
72
|
const pageFolder = String(pageFolderRaw ?? '').trim();
|
|
86
73
|
targetDir = path.join(targetDir, pageFolder);
|
|
87
74
|
}
|
|
88
|
-
|
|
89
75
|
targetDir = path.join(targetDir, name);
|
|
90
|
-
|
|
91
76
|
await fs.mkdir(targetDir, { recursive: true });
|
|
92
77
|
await writeIfNotExists(path.join(targetDir, 'Component.svelte'), createComponentContent(propsName));
|
|
93
78
|
await writeIfNotExists(path.join(targetDir, 'style.scss'), '');
|
|
94
79
|
await writeIfNotExists(path.join(targetDir, 'types.ts'), createTypesContent(propsName));
|
|
95
80
|
await writeIfNotExists(path.join(targetDir, 'index.ts'), createIndexContent(name));
|
|
96
|
-
|
|
97
81
|
console.log(`✅ Component generated: ${targetDir}`);
|
|
98
82
|
}
|