@azure-net/cli 5.0.0-1 → 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 +12 -45
- package/bin/azure-net.ts +13 -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 +2 -1
- package/src/checks/checkInternal.ts +2 -0
- package/src/checks/checkLayerBoundaries.js +50 -54
- package/src/checks/checkLayerBoundaries.ts +55 -58
- package/src/checks/checkLayerImports.js +136 -0
- package/src/checks/checkLayerImports.ts +185 -0
- 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 +1 -1
- package/src/checks/index.ts +1 -0
- 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,63 +5,61 @@ import { toKebabCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
|
5
5
|
import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
|
|
6
6
|
import { getConfigState, resolveContextAlias, selectContext } from './repositoryModuleShared.js';
|
|
7
7
|
import { loadUserConfig } from '../../utils/loadConfig.js';
|
|
8
|
-
|
|
8
|
+
import { getFoundationConstructImportPath, getFoundationConstructPath, getSharedState } from '../../utils/sharedFoundation.js';
|
|
9
9
|
const PRIMITIVE_TYPES = new Set(['string', 'number', 'boolean', 'unknown', 'void', 'null', 'undefined', 'bigint', 'symbol', 'never']);
|
|
10
|
-
|
|
11
10
|
const parseProviderInfo = (content) => {
|
|
12
11
|
const providerNameMatch = content.match(/export\s+const\s+(\w+)\s*=\s*createBoundaryProvider/);
|
|
13
12
|
const providerName = providerNameMatch?.[1] ?? 'ApplicationProvider';
|
|
14
|
-
|
|
15
13
|
const registerMatch = content.match(/register\s*:\s*\([^)]*\)\s*=>\s*\(\{([\s\S]*?)\}\)/m);
|
|
16
14
|
const registerBody = registerMatch?.[1] ?? '';
|
|
17
15
|
const keys = [];
|
|
18
16
|
const keyRegex = /([A-Za-z_]\w*)\s*:/g;
|
|
19
17
|
let keyMatch;
|
|
20
18
|
while ((keyMatch = keyRegex.exec(registerBody)) !== null) {
|
|
21
|
-
|
|
19
|
+
const key = keyMatch[1];
|
|
20
|
+
if (key) keys.push(key);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
22
|
return { providerName, registeredKeys: keys };
|
|
25
23
|
};
|
|
26
|
-
|
|
27
24
|
const parseUseCasesInfo = (content) => {
|
|
28
25
|
const interfaceImport = content.match(/import\s+type\s+\{\s*(\w+)\s*\}\s+from\s+['"]([^'"]+)['"]/);
|
|
29
26
|
const repositoryInterfaceName = interfaceImport?.[1] ?? null;
|
|
30
27
|
const domainImportPath = interfaceImport?.[2] ?? null;
|
|
31
28
|
const domainNameMatch = domainImportPath?.match(/\/layers\/domain\/([^'"]+)$/);
|
|
32
29
|
const domainName = domainNameMatch?.[1] ?? null;
|
|
33
|
-
|
|
34
30
|
const methods = [];
|
|
35
31
|
const declareRegex = /declare\s+(\w+)\s*:\s*\w+\['\w+'\];/g;
|
|
36
32
|
let declareMatch;
|
|
37
33
|
while ((declareMatch = declareRegex.exec(content)) !== null) {
|
|
38
|
-
|
|
34
|
+
const methodName = declareMatch[1];
|
|
35
|
+
if (methodName) methods.push(methodName);
|
|
39
36
|
}
|
|
40
|
-
|
|
41
37
|
return { repositoryInterfaceName, domainName, methods };
|
|
42
38
|
};
|
|
43
|
-
|
|
44
39
|
const parseRepositoryInterfaceMethods = (content) => {
|
|
45
40
|
const methods = [];
|
|
46
41
|
const regex = /(\w+)\(([^)]*)\)\s*:\s*Promise<([^>]+)>;/g;
|
|
47
42
|
let match;
|
|
48
43
|
while ((match = regex.exec(content)) !== null) {
|
|
49
|
-
const
|
|
44
|
+
const methodName = match[1];
|
|
45
|
+
const paramsChunk = match[2];
|
|
46
|
+
const responseTypeChunk = match[3];
|
|
47
|
+
if (!methodName || paramsChunk === undefined || !responseTypeChunk) continue;
|
|
48
|
+
const paramsRaw = paramsChunk.trim();
|
|
50
49
|
const params = paramsRaw
|
|
51
50
|
? paramsRaw.split(',').map((part) => {
|
|
52
51
|
const [name, ...typeParts] = part.trim().split(':');
|
|
53
|
-
return { name: name.trim(), type: typeParts.join(':').trim() };
|
|
52
|
+
return { name: String(name ?? '').trim(), type: typeParts.join(':').trim() };
|
|
54
53
|
})
|
|
55
54
|
: [];
|
|
56
55
|
methods.push({
|
|
57
|
-
name:
|
|
56
|
+
name: methodName,
|
|
58
57
|
params,
|
|
59
|
-
responseType:
|
|
58
|
+
responseType: responseTypeChunk.trim()
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
return methods;
|
|
63
62
|
};
|
|
64
|
-
|
|
65
63
|
const findRepositoryByInterface = async (contextName, repositoryInterfaceName) => {
|
|
66
64
|
const repositoriesPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'infrastructure', 'http', 'repositories');
|
|
67
65
|
try {
|
|
@@ -69,7 +67,6 @@ const findRepositoryByInterface = async (contextName, repositoryInterfaceName) =
|
|
|
69
67
|
for (const fileName of files) {
|
|
70
68
|
if (!fileName.endsWith('.ts') || fileName === 'index.ts') continue;
|
|
71
69
|
const absolute = path.join(repositoriesPath, fileName);
|
|
72
|
-
// eslint-disable-next-line no-await-in-loop
|
|
73
70
|
const content = await fs.readFile(absolute, 'utf-8');
|
|
74
71
|
if (content.includes(`implements ${repositoryInterfaceName}`)) {
|
|
75
72
|
return { filePath: absolute, content };
|
|
@@ -80,22 +77,22 @@ const findRepositoryByInterface = async (contextName, repositoryInterfaceName) =
|
|
|
80
77
|
return null;
|
|
81
78
|
}
|
|
82
79
|
};
|
|
83
|
-
|
|
84
80
|
const parseRepositoryHttpMethods = (content) => {
|
|
85
81
|
const methods = new Map();
|
|
86
82
|
const regex = /public\s+async\s+(\w+)\([^)]*\)\s*\{([\s\S]*?)\n\t\}/g;
|
|
87
83
|
let match;
|
|
88
84
|
while ((match = regex.exec(content)) !== null) {
|
|
89
85
|
const methodName = match[1];
|
|
86
|
+
if (!methodName) continue;
|
|
90
87
|
const body = match[2] ?? '';
|
|
91
88
|
const httpMatch = body.match(/http\.(get|post|put|delete|head|options|patch)\(/);
|
|
92
|
-
if (httpMatch) methods.set(methodName, httpMatch[1]);
|
|
89
|
+
if (httpMatch?.[1]) methods.set(methodName, httpMatch[1]);
|
|
93
90
|
}
|
|
94
91
|
return methods;
|
|
95
92
|
};
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
const presenterPath =
|
|
93
|
+
const getSharedPresenterFactories = async () => {
|
|
94
|
+
const { sharedContext } = await getSharedState();
|
|
95
|
+
const presenterPath = getFoundationConstructPath(sharedContext.name, 'presenter');
|
|
99
96
|
try {
|
|
100
97
|
const files = await fs.readdir(presenterPath);
|
|
101
98
|
return files.filter((item) => item.endsWith('.ts') && item !== 'index.ts').map((item) => item.replace(/\.ts$/, ''));
|
|
@@ -103,9 +100,9 @@ const getCorePresenterFactories = async () => {
|
|
|
103
100
|
return [];
|
|
104
101
|
}
|
|
105
102
|
};
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const schemaPath =
|
|
103
|
+
const getSharedSchemaFactories = async () => {
|
|
104
|
+
const { sharedContext } = await getSharedState();
|
|
105
|
+
const schemaPath = getFoundationConstructPath(sharedContext.name, 'schema');
|
|
109
106
|
try {
|
|
110
107
|
const files = await fs.readdir(schemaPath);
|
|
111
108
|
return files.filter((item) => item.endsWith('.ts') && item !== 'index.ts').map((item) => item.replace(/\.ts$/, ''));
|
|
@@ -113,19 +110,15 @@ const getCoreSchemaFactories = async () => {
|
|
|
113
110
|
return [];
|
|
114
111
|
}
|
|
115
112
|
};
|
|
116
|
-
|
|
117
113
|
const normalizePresenterName = (rawName, fallbackUseCasesName) => {
|
|
118
114
|
const fallback = fallbackUseCasesName.replace(/UseCases$/, '') || 'Module';
|
|
119
115
|
const source = String(rawName ?? '').trim() || fallback;
|
|
120
116
|
const normalized = toPascalCase(source);
|
|
121
117
|
return normalized.endsWith('Presenter') ? normalized : `${normalized}Presenter`;
|
|
122
118
|
};
|
|
123
|
-
|
|
124
119
|
const isPrimitive = (typeName) => PRIMITIVE_TYPES.has(String(typeName ?? '').trim());
|
|
125
|
-
|
|
126
120
|
const normalizeTypeImports = (types) => Array.from(new Set(types.filter((typeName) => !isPrimitive(typeName))));
|
|
127
121
|
const isPathParamName = (name) => name === 'pathParam' || name === 'path';
|
|
128
|
-
|
|
129
122
|
const buildMethodInvocation = (method, useCasesName, schemaByMethod) => {
|
|
130
123
|
const paramsSignature = method.params.map((item) => `${item.name}: ${item.type}`).join(', ');
|
|
131
124
|
const argNames = method.params.map((item) => item.name);
|
|
@@ -137,21 +130,17 @@ const buildMethodInvocation = (method, useCasesName, schemaByMethod) => {
|
|
|
137
130
|
}
|
|
138
131
|
return argName;
|
|
139
132
|
});
|
|
140
|
-
|
|
141
133
|
const httpMethod = method.httpMethod;
|
|
142
134
|
if (httpMethod === 'get' || httpMethod === 'head' || httpMethod === 'options') {
|
|
143
135
|
return `\tconst ${method.name} = (${paramsSignature}) => createAsyncResource(() => ${useCasesName}.${method.name}(${args.join(', ')}));`;
|
|
144
136
|
}
|
|
145
|
-
|
|
146
137
|
const responseType = method.responseType;
|
|
147
138
|
const requestType = requestParam?.type ?? null;
|
|
148
139
|
let generic = `<${responseType}`;
|
|
149
140
|
if (requestType) generic += `, ${requestType}`;
|
|
150
141
|
generic += '>';
|
|
151
|
-
|
|
152
142
|
return `\tconst ${method.name} = async (${paramsSignature}) =>\n\t\tawait createAsyncAction${generic}(() => ${useCasesName}.${method.name}(${args.join(', ')}));`;
|
|
153
143
|
};
|
|
154
|
-
|
|
155
144
|
const getApplicationProviderFile = async (contextName) => {
|
|
156
145
|
const providersPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'application', 'providers');
|
|
157
146
|
const targetPath = path.join(providersPath, 'ApplicationProvider.ts');
|
|
@@ -164,7 +153,6 @@ const getApplicationProviderFile = async (contextName) => {
|
|
|
164
153
|
return candidate ? path.join(providersPath, candidate) : null;
|
|
165
154
|
}
|
|
166
155
|
};
|
|
167
|
-
|
|
168
156
|
export default async function generatePresenter(options = {}) {
|
|
169
157
|
let contextName = options.contextName ? String(options.contextName) : null;
|
|
170
158
|
if (!contextName) {
|
|
@@ -174,17 +162,14 @@ export default async function generatePresenter(options = {}) {
|
|
|
174
162
|
process.exitCode = 1;
|
|
175
163
|
return;
|
|
176
164
|
}
|
|
177
|
-
|
|
178
165
|
const appProviderFile = await getApplicationProviderFile(contextName);
|
|
179
166
|
if (!appProviderFile) {
|
|
180
167
|
console.error('❌ ApplicationProvider file was not found.');
|
|
181
168
|
process.exitCode = 1;
|
|
182
169
|
return;
|
|
183
170
|
}
|
|
184
|
-
|
|
185
171
|
const appProviderContent = await fs.readFile(appProviderFile, 'utf-8');
|
|
186
172
|
const providerInfo = parseProviderInfo(appProviderContent);
|
|
187
|
-
|
|
188
173
|
const useCasesPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'application', 'use-cases');
|
|
189
174
|
let useCasesFiles = [];
|
|
190
175
|
try {
|
|
@@ -194,14 +179,12 @@ export default async function generatePresenter(options = {}) {
|
|
|
194
179
|
} catch {
|
|
195
180
|
// ignore
|
|
196
181
|
}
|
|
197
|
-
|
|
198
182
|
const registeredUseCases = useCasesFiles.filter((item) => providerInfo.registeredKeys.includes(item));
|
|
199
183
|
if (!registeredUseCases.length) {
|
|
200
184
|
console.error('❌ No registered UseCases were found in ApplicationProvider register().');
|
|
201
185
|
process.exitCode = 1;
|
|
202
186
|
return;
|
|
203
187
|
}
|
|
204
|
-
|
|
205
188
|
let useCasesName = options.useCasesName ? String(options.useCasesName) : '';
|
|
206
189
|
if (!useCasesName) {
|
|
207
190
|
const { selectedUseCases } = await prompts({
|
|
@@ -213,13 +196,11 @@ export default async function generatePresenter(options = {}) {
|
|
|
213
196
|
});
|
|
214
197
|
useCasesName = String(selectedUseCases ?? '');
|
|
215
198
|
}
|
|
216
|
-
|
|
217
199
|
if (!registeredUseCases.includes(useCasesName)) {
|
|
218
200
|
console.error(`❌ UseCases "${useCasesName}" is not registered in ApplicationProvider.`);
|
|
219
201
|
process.exitCode = 1;
|
|
220
202
|
return;
|
|
221
203
|
}
|
|
222
|
-
|
|
223
204
|
const useCasesFile = path.join(useCasesPath, `${useCasesName}.ts`);
|
|
224
205
|
const useCasesContent = await fs.readFile(useCasesFile, 'utf-8');
|
|
225
206
|
const useCasesInfo = parseUseCasesInfo(useCasesContent);
|
|
@@ -228,14 +209,12 @@ export default async function generatePresenter(options = {}) {
|
|
|
228
209
|
process.exitCode = 1;
|
|
229
210
|
return;
|
|
230
211
|
}
|
|
231
|
-
|
|
232
212
|
const repositoryInfo = await findRepositoryByInterface(contextName, useCasesInfo.repositoryInterfaceName);
|
|
233
213
|
if (!repositoryInfo) {
|
|
234
214
|
console.error(`❌ Repository implementing ${useCasesInfo.repositoryInterfaceName} was not found.`);
|
|
235
215
|
process.exitCode = 1;
|
|
236
216
|
return;
|
|
237
217
|
}
|
|
238
|
-
|
|
239
218
|
const repositoryHttpMethods = parseRepositoryHttpMethods(repositoryInfo.content);
|
|
240
219
|
const domainPortsInterfacePath = path.join(
|
|
241
220
|
process.cwd(),
|
|
@@ -252,25 +231,23 @@ export default async function generatePresenter(options = {}) {
|
|
|
252
231
|
const repositoryMethods = parseRepositoryInterfaceMethods(repositoryInterfaceContent).filter((method) =>
|
|
253
232
|
useCasesInfo.methods.includes(method.name)
|
|
254
233
|
);
|
|
255
|
-
|
|
256
234
|
const { presenterNameRaw } = await prompts({
|
|
257
235
|
type: 'text',
|
|
258
236
|
name: 'presenterNameRaw',
|
|
259
237
|
message: 'Presenter name (optional):'
|
|
260
238
|
});
|
|
261
|
-
|
|
262
239
|
const presenterName = normalizePresenterName(presenterNameRaw, useCasesName);
|
|
263
240
|
const presenterFolder = toKebabCase(presenterName.replace(/Presenter$/, ''));
|
|
264
241
|
const presentationPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'presentation', presenterFolder);
|
|
265
242
|
await fs.mkdir(presentationPath, { recursive: true });
|
|
266
|
-
|
|
267
|
-
const { contexts, coreAlias } = await getConfigState();
|
|
243
|
+
const { contexts, sharedAlias } = await getConfigState();
|
|
268
244
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
269
|
-
|
|
245
|
+
const presenterImportPath = getFoundationConstructImportPath(sharedAlias, 'presenter');
|
|
246
|
+
const schemaFactoryImportPath = getFoundationConstructImportPath(sharedAlias, 'schema');
|
|
270
247
|
let presenterFactory = String((await loadUserConfig()).defaultPresenterFactory ?? '').trim();
|
|
271
248
|
let useNativePresenter = false;
|
|
272
249
|
if (!presenterFactory) {
|
|
273
|
-
const availableFactories = await
|
|
250
|
+
const availableFactories = await getSharedPresenterFactories();
|
|
274
251
|
const { selectedFactory } = await prompts({
|
|
275
252
|
type: 'select',
|
|
276
253
|
name: 'selectedFactory',
|
|
@@ -281,7 +258,6 @@ export default async function generatePresenter(options = {}) {
|
|
|
281
258
|
],
|
|
282
259
|
initial: 0
|
|
283
260
|
});
|
|
284
|
-
|
|
285
261
|
if (selectedFactory === '__native__') {
|
|
286
262
|
useNativePresenter = true;
|
|
287
263
|
presenterFactory = 'createPresenter';
|
|
@@ -289,46 +265,38 @@ export default async function generatePresenter(options = {}) {
|
|
|
289
265
|
presenterFactory = String(selectedFactory ?? '');
|
|
290
266
|
}
|
|
291
267
|
}
|
|
292
|
-
|
|
293
268
|
if (!presenterFactory) {
|
|
294
269
|
useNativePresenter = true;
|
|
295
270
|
presenterFactory = 'createPresenter';
|
|
296
271
|
}
|
|
297
|
-
|
|
298
272
|
const mutationMethods = repositoryMethods.filter((method) => {
|
|
299
273
|
const httpMethod = repositoryHttpMethods.get(method.name) ?? 'get';
|
|
300
274
|
return httpMethod !== 'get' && httpMethod !== 'head' && httpMethod !== 'options';
|
|
301
275
|
});
|
|
302
|
-
|
|
303
276
|
let schemaFactory = String((await loadUserConfig()).defaultSchemaFactory ?? '').trim();
|
|
304
277
|
let schemaFactoryResolved = false;
|
|
305
278
|
const schemaByMethod = new Map();
|
|
306
279
|
const schemaImports = [];
|
|
307
280
|
let schemaIndexNeeded = false;
|
|
308
|
-
|
|
309
281
|
if (mutationMethods.length) {
|
|
310
282
|
for (const method of mutationMethods) {
|
|
311
283
|
const requestParam = method.params.find((item) => !isPathParamName(item.name));
|
|
312
284
|
if (!requestParam) continue;
|
|
313
|
-
|
|
314
285
|
const { shouldCreateSchema } = await prompts({
|
|
315
286
|
type: 'confirm',
|
|
316
287
|
name: 'shouldCreateSchema',
|
|
317
288
|
message: `Create schema for "${method.name}"?`,
|
|
318
289
|
initial: false
|
|
319
290
|
});
|
|
320
|
-
|
|
321
291
|
if (!shouldCreateSchema) continue;
|
|
322
|
-
|
|
323
292
|
if (!schemaFactoryResolved) {
|
|
324
293
|
if (!schemaFactory) {
|
|
325
|
-
const availableSchemas = await
|
|
294
|
+
const availableSchemas = await getSharedSchemaFactories();
|
|
326
295
|
if (!availableSchemas.length) {
|
|
327
|
-
console.error('❌ No schema factories found in
|
|
296
|
+
console.error('❌ No schema factories found in shared foundation constructs/schema.');
|
|
328
297
|
process.exitCode = 1;
|
|
329
298
|
return;
|
|
330
299
|
}
|
|
331
|
-
|
|
332
300
|
const { selectedSchemaFactory } = await prompts({
|
|
333
301
|
type: 'select',
|
|
334
302
|
name: 'selectedSchemaFactory',
|
|
@@ -336,37 +304,30 @@ export default async function generatePresenter(options = {}) {
|
|
|
336
304
|
choices: availableSchemas.map((item) => ({ title: item, value: item })),
|
|
337
305
|
initial: 0
|
|
338
306
|
});
|
|
339
|
-
|
|
340
307
|
schemaFactory = String(selectedSchemaFactory ?? '');
|
|
341
308
|
}
|
|
342
|
-
|
|
343
309
|
schemaFactoryResolved = true;
|
|
344
310
|
}
|
|
345
|
-
|
|
346
311
|
const schemaConstName = `${toPascalCase(method.name)}${presenterName}Schema`;
|
|
347
312
|
const schemaFileName = `${schemaConstName}.ts`;
|
|
348
313
|
const schemaPath = path.join(presentationPath, 'schema');
|
|
349
314
|
await fs.mkdir(schemaPath, { recursive: true });
|
|
350
315
|
schemaIndexNeeded = true;
|
|
351
|
-
|
|
352
|
-
const schemaContent = `import { ${schemaFactory} } from '${coreAlias}/schema';
|
|
316
|
+
const schemaContent = `import { ${schemaFactory} } from '${schemaFactoryImportPath}';
|
|
353
317
|
import type { ${requestParam.type} } from '${contextAlias}/layers/domain/${useCasesInfo.domainName}';
|
|
354
318
|
|
|
355
319
|
export const ${schemaConstName} = ${schemaFactory}<${requestParam.type}>()
|
|
356
320
|
\t.rules((rules) => ({}))
|
|
357
321
|
\t.create();
|
|
358
322
|
`;
|
|
359
|
-
|
|
360
323
|
await writeIfNotExists(path.join(schemaPath, schemaFileName), schemaContent);
|
|
361
324
|
schemaByMethod.set(method.name, { constName: schemaConstName });
|
|
362
325
|
schemaImports.push(schemaConstName);
|
|
363
326
|
}
|
|
364
327
|
}
|
|
365
|
-
|
|
366
328
|
if (schemaIndexNeeded) {
|
|
367
329
|
await updateIndexTs(path.join(presentationPath, 'schema'));
|
|
368
330
|
}
|
|
369
|
-
|
|
370
331
|
const hasGetLike = repositoryMethods.some((method) => {
|
|
371
332
|
const httpMethod = repositoryHttpMethods.get(method.name) ?? 'get';
|
|
372
333
|
return httpMethod === 'get' || httpMethod === 'head' || httpMethod === 'options';
|
|
@@ -375,29 +336,23 @@ export const ${schemaConstName} = ${schemaFactory}<${requestParam.type}>()
|
|
|
375
336
|
const httpMethod = repositoryHttpMethods.get(method.name) ?? 'get';
|
|
376
337
|
return httpMethod !== 'get' && httpMethod !== 'head' && httpMethod !== 'options';
|
|
377
338
|
});
|
|
378
|
-
|
|
379
339
|
const helperParams = [hasGetLike ? 'createAsyncResource' : null, hasMutation ? 'createAsyncAction' : null].filter(Boolean).join(', ');
|
|
380
|
-
|
|
381
340
|
const typeImports = normalizeTypeImports(
|
|
382
341
|
repositoryMethods.flatMap((method) => [method.responseType, ...method.params.map((item) => item.type)])
|
|
383
342
|
);
|
|
384
|
-
|
|
385
343
|
const methodsCode = repositoryMethods
|
|
386
344
|
.map((method) => {
|
|
387
345
|
const httpMethod = repositoryHttpMethods.get(method.name) ?? 'get';
|
|
388
346
|
return buildMethodInvocation({ ...method, httpMethod }, useCasesName, schemaByMethod);
|
|
389
347
|
})
|
|
390
348
|
.join('\n\n');
|
|
391
|
-
|
|
392
349
|
const factoryImport = useNativePresenter
|
|
393
350
|
? `import { createPresenter } from '@azure-net/kit/delivery';`
|
|
394
|
-
: `import { ${presenterFactory} } from '${
|
|
395
|
-
|
|
351
|
+
: `import { ${presenterFactory} } from '${presenterImportPath}';`;
|
|
396
352
|
const schemaImportLine = schemaImports.length ? `import { ${Array.from(new Set(schemaImports)).join(', ')} } from './schema';` : '';
|
|
397
353
|
const typeImportLine = typeImports.length
|
|
398
354
|
? `import type { ${typeImports.join(', ')} } from '${contextAlias}/layers/domain/${useCasesInfo.domainName}';`
|
|
399
355
|
: '';
|
|
400
|
-
|
|
401
356
|
const presenterId = `${toPascalCase(contextName)}${presenterName}`;
|
|
402
357
|
const presenterContent = `${factoryImport}
|
|
403
358
|
import { ${providerInfo.providerName} } from '${contextAlias}/layers/application/providers';
|
|
@@ -411,9 +366,7 @@ ${methodsCode ? `${methodsCode}\n` : ''}
|
|
|
411
366
|
\treturn { ${repositoryMethods.map((method) => method.name).join(', ')} };
|
|
412
367
|
});
|
|
413
368
|
`;
|
|
414
|
-
|
|
415
369
|
await writeIfNotExists(path.join(presentationPath, `${presenterName}.ts`), presenterContent);
|
|
416
370
|
await fs.writeFile(path.join(presentationPath, 'index.ts'), `export * from './${presenterName}';\n`, 'utf-8');
|
|
417
|
-
|
|
418
371
|
console.log(`✅ Presenter generated: ${path.join(presentationPath, `${presenterName}.ts`)}`);
|
|
419
372
|
}
|
|
@@ -5,6 +5,7 @@ import { toKebabCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
|
5
5
|
import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
|
|
6
6
|
import { getConfigState, resolveContextAlias, selectContext } from './repositoryModuleShared.js';
|
|
7
7
|
import { loadUserConfig } from '../../utils/loadConfig.js';
|
|
8
|
+
import { getFoundationConstructImportPath, getFoundationConstructPath, getSharedState } from '../../utils/sharedFoundation.js';
|
|
8
9
|
|
|
9
10
|
type MethodParam = { name: string; type: string };
|
|
10
11
|
type RepositoryMethod = { name: string; params: MethodParam[]; responseType: string };
|
|
@@ -108,8 +109,9 @@ const parseRepositoryHttpMethods = (content: string): Map<string, string> => {
|
|
|
108
109
|
return methods;
|
|
109
110
|
};
|
|
110
111
|
|
|
111
|
-
const
|
|
112
|
-
const
|
|
112
|
+
const getSharedPresenterFactories = async (): Promise<string[]> => {
|
|
113
|
+
const { sharedContext } = await getSharedState();
|
|
114
|
+
const presenterPath = getFoundationConstructPath(sharedContext.name, 'presenter');
|
|
113
115
|
try {
|
|
114
116
|
const files = await fs.readdir(presenterPath);
|
|
115
117
|
return files.filter((item) => item.endsWith('.ts') && item !== 'index.ts').map((item) => item.replace(/\.ts$/, ''));
|
|
@@ -118,8 +120,9 @@ const getCorePresenterFactories = async (): Promise<string[]> => {
|
|
|
118
120
|
}
|
|
119
121
|
};
|
|
120
122
|
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
+
const getSharedSchemaFactories = async (): Promise<string[]> => {
|
|
124
|
+
const { sharedContext } = await getSharedState();
|
|
125
|
+
const schemaPath = getFoundationConstructPath(sharedContext.name, 'schema');
|
|
123
126
|
try {
|
|
124
127
|
const files = await fs.readdir(schemaPath);
|
|
125
128
|
return files.filter((item) => item.endsWith('.ts') && item !== 'index.ts').map((item) => item.replace(/\.ts$/, ''));
|
|
@@ -287,13 +290,15 @@ export default async function generatePresenter(options: GeneratePresenterOption
|
|
|
287
290
|
const presentationPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'presentation', presenterFolder);
|
|
288
291
|
await fs.mkdir(presentationPath, { recursive: true });
|
|
289
292
|
|
|
290
|
-
const { contexts,
|
|
293
|
+
const { contexts, sharedAlias } = await getConfigState();
|
|
291
294
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
295
|
+
const presenterImportPath = getFoundationConstructImportPath(sharedAlias, 'presenter');
|
|
296
|
+
const schemaFactoryImportPath = getFoundationConstructImportPath(sharedAlias, 'schema');
|
|
292
297
|
|
|
293
298
|
let presenterFactory = String((await loadUserConfig()).defaultPresenterFactory ?? '').trim();
|
|
294
299
|
let useNativePresenter = false;
|
|
295
300
|
if (!presenterFactory) {
|
|
296
|
-
const availableFactories = await
|
|
301
|
+
const availableFactories = await getSharedPresenterFactories();
|
|
297
302
|
const { selectedFactory } = await prompts({
|
|
298
303
|
type: 'select',
|
|
299
304
|
name: 'selectedFactory',
|
|
@@ -345,9 +350,9 @@ export default async function generatePresenter(options: GeneratePresenterOption
|
|
|
345
350
|
|
|
346
351
|
if (!schemaFactoryResolved) {
|
|
347
352
|
if (!schemaFactory) {
|
|
348
|
-
const availableSchemas = await
|
|
353
|
+
const availableSchemas = await getSharedSchemaFactories();
|
|
349
354
|
if (!availableSchemas.length) {
|
|
350
|
-
console.error('❌ No schema factories found in
|
|
355
|
+
console.error('❌ No schema factories found in shared foundation constructs/schema.');
|
|
351
356
|
process.exitCode = 1;
|
|
352
357
|
return;
|
|
353
358
|
}
|
|
@@ -372,7 +377,7 @@ export default async function generatePresenter(options: GeneratePresenterOption
|
|
|
372
377
|
await fs.mkdir(schemaPath, { recursive: true });
|
|
373
378
|
schemaIndexNeeded = true;
|
|
374
379
|
|
|
375
|
-
const schemaContent = `import { ${schemaFactory} } from '${
|
|
380
|
+
const schemaContent = `import { ${schemaFactory} } from '${schemaFactoryImportPath}';
|
|
376
381
|
import type { ${requestParam.type} } from '${contextAlias}/layers/domain/${useCasesInfo.domainName}';
|
|
377
382
|
|
|
378
383
|
export const ${schemaConstName} = ${schemaFactory}<${requestParam.type}>()
|
|
@@ -414,7 +419,7 @@ export const ${schemaConstName} = ${schemaFactory}<${requestParam.type}>()
|
|
|
414
419
|
|
|
415
420
|
const factoryImport = useNativePresenter
|
|
416
421
|
? `import { createPresenter } from '@azure-net/kit/delivery';`
|
|
417
|
-
: `import { ${presenterFactory} } from '${
|
|
422
|
+
: `import { ${presenterFactory} } from '${presenterImportPath}';`;
|
|
418
423
|
|
|
419
424
|
const schemaImportLine = schemaImports.length ? `import { ${Array.from(new Set(schemaImports)).join(', ')} } from './schema';` : '';
|
|
420
425
|
const typeImportLine = typeImports.length
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
writeRepositoryInterface
|
|
18
18
|
} from './repositoryModuleShared.js';
|
|
19
19
|
import { updateIndexTs, writeIfNotExists } from '../../utils/fileUtils.js';
|
|
20
|
-
|
|
21
20
|
export default async function generateRepository(options = {}) {
|
|
22
21
|
let contextName = options.contextName ? String(options.contextName) : null;
|
|
23
22
|
if (!contextName) {
|
|
@@ -27,7 +26,6 @@ export default async function generateRepository(options = {}) {
|
|
|
27
26
|
process.exitCode = 1;
|
|
28
27
|
return null;
|
|
29
28
|
}
|
|
30
|
-
|
|
31
29
|
let domainName = options.domainName ? String(options.domainName) : null;
|
|
32
30
|
if (!domainName) {
|
|
33
31
|
domainName = await selectDomain(contextName);
|
|
@@ -36,23 +34,19 @@ export default async function generateRepository(options = {}) {
|
|
|
36
34
|
process.exitCode = 1;
|
|
37
35
|
return null;
|
|
38
36
|
}
|
|
39
|
-
|
|
40
37
|
const { repositoryNameRaw } = await prompts({
|
|
41
38
|
type: 'text',
|
|
42
39
|
name: 'repositoryNameRaw',
|
|
43
40
|
message: 'Repository name:',
|
|
44
41
|
validate: (value) => (String(value ?? '').trim().length > 0 ? true : 'Repository name is required')
|
|
45
42
|
});
|
|
46
|
-
|
|
47
43
|
const className = ensureRepositoryName(repositoryNameRaw);
|
|
48
44
|
const meta = getRepositoryMeta(className);
|
|
49
|
-
|
|
50
45
|
const datasource = await selectDatasource(contextName);
|
|
51
46
|
if (!datasource) {
|
|
52
47
|
process.exitCode = 1;
|
|
53
48
|
return null;
|
|
54
49
|
}
|
|
55
|
-
|
|
56
50
|
const domainTypes = await getDomainTypes(contextName, domainName);
|
|
57
51
|
const { methodsCountRaw } = await prompts({
|
|
58
52
|
type: 'number',
|
|
@@ -61,33 +55,28 @@ export default async function generateRepository(options = {}) {
|
|
|
61
55
|
initial: 1,
|
|
62
56
|
min: 0
|
|
63
57
|
});
|
|
64
|
-
|
|
65
58
|
const methodsCount = Number(methodsCountRaw ?? 0);
|
|
66
59
|
if (!Number.isFinite(methodsCount) || methodsCount < 0) {
|
|
67
60
|
console.error('❌ Invalid methods count.');
|
|
68
61
|
process.exitCode = 1;
|
|
69
62
|
return null;
|
|
70
63
|
}
|
|
71
|
-
|
|
72
64
|
const methods = await promptMethodsBatch(domainTypes, methodsCount);
|
|
73
65
|
if (!methods) {
|
|
74
66
|
process.exitCode = 1;
|
|
75
67
|
return null;
|
|
76
68
|
}
|
|
77
|
-
|
|
78
69
|
const { contexts } = await getConfigState();
|
|
79
70
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
80
71
|
const repositoriesPath = resolveRepositoriesPath(contextName);
|
|
81
72
|
await fs.mkdir(repositoriesPath, { recursive: true });
|
|
82
73
|
const repositoryPath = path.join(repositoriesPath, `${meta.className}.ts`);
|
|
83
|
-
|
|
84
74
|
await writeRepositoryInterface({
|
|
85
75
|
contextName,
|
|
86
76
|
domainName,
|
|
87
77
|
meta,
|
|
88
78
|
methods
|
|
89
79
|
});
|
|
90
|
-
|
|
91
80
|
const repositoryContent = buildRepositoryContent({
|
|
92
81
|
meta,
|
|
93
82
|
contextAlias,
|
|
@@ -95,23 +84,19 @@ export default async function generateRepository(options = {}) {
|
|
|
95
84
|
datasource,
|
|
96
85
|
methods
|
|
97
86
|
});
|
|
98
|
-
|
|
99
87
|
const created = await writeIfNotExists(repositoryPath, repositoryContent);
|
|
100
88
|
await updateIndexTs(repositoriesPath);
|
|
101
|
-
|
|
102
89
|
if (!created) {
|
|
103
90
|
console.warn(`⚠️ Repository "${meta.className}" already exists. File was not overwritten.`);
|
|
104
91
|
process.exitCode = 1;
|
|
105
92
|
return null;
|
|
106
93
|
}
|
|
107
|
-
|
|
108
94
|
const { shouldCreateUseCases } = await prompts({
|
|
109
95
|
type: 'confirm',
|
|
110
96
|
name: 'shouldCreateUseCases',
|
|
111
97
|
message: 'Create UseCases for this repository?',
|
|
112
98
|
initial: true
|
|
113
99
|
});
|
|
114
|
-
|
|
115
100
|
let useCasesCreated = false;
|
|
116
101
|
if (shouldCreateUseCases) {
|
|
117
102
|
await createUseCasesForRepository({
|
|
@@ -122,7 +107,6 @@ export default async function generateRepository(options = {}) {
|
|
|
122
107
|
});
|
|
123
108
|
useCasesCreated = true;
|
|
124
109
|
}
|
|
125
|
-
|
|
126
110
|
if (!shouldCreateUseCases && options.forceCreateUseCases) {
|
|
127
111
|
await createUseCasesForRepository({
|
|
128
112
|
contextName,
|
|
@@ -132,7 +116,6 @@ export default async function generateRepository(options = {}) {
|
|
|
132
116
|
});
|
|
133
117
|
useCasesCreated = true;
|
|
134
118
|
}
|
|
135
|
-
|
|
136
119
|
console.log(`✅ Repository generated: ${repositoryPath}`);
|
|
137
120
|
return {
|
|
138
121
|
contextName,
|