@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,20 +5,32 @@ import { loadUserConfig } from '../../utils/loadConfig.js';
|
|
|
5
5
|
import { normalizeContexts } from '../../init/initAliases.js';
|
|
6
6
|
import { toCamelCase, toKebabCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
7
7
|
import { updateIndexTs, writeIfNotExists } from '../../utils/fileUtils.js';
|
|
8
|
-
|
|
8
|
+
import { getFoundationConstructImportPath, getFoundationConstructPath, getSharedState } from '../../utils/sharedFoundation.js';
|
|
9
|
+
const toHttpMethod = (value) => {
|
|
10
|
+
const normalized = String(value ?? '').toLowerCase();
|
|
11
|
+
if (
|
|
12
|
+
normalized === 'get' ||
|
|
13
|
+
normalized === 'post' ||
|
|
14
|
+
normalized === 'put' ||
|
|
15
|
+
normalized === 'delete' ||
|
|
16
|
+
normalized === 'head' ||
|
|
17
|
+
normalized === 'options' ||
|
|
18
|
+
normalized === 'patch'
|
|
19
|
+
) {
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
return 'get';
|
|
23
|
+
};
|
|
9
24
|
const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'];
|
|
10
|
-
|
|
11
25
|
const normalizeAlias = (value, fallback) => {
|
|
12
26
|
const raw = String(value ?? '').trim();
|
|
13
27
|
if (!raw.length) return fallback;
|
|
14
28
|
return raw.startsWith('$') ? raw : `$${raw}`;
|
|
15
29
|
};
|
|
16
|
-
|
|
17
30
|
export const ensureRepositoryName = (rawName) => {
|
|
18
31
|
const normalized = toPascalCase(String(rawName ?? '').trim() || 'Repository');
|
|
19
32
|
return normalized.endsWith('Repository') ? normalized : `${normalized}Repository`;
|
|
20
33
|
};
|
|
21
|
-
|
|
22
34
|
const RESERVED_IDENTIFIERS = new Set([
|
|
23
35
|
'break',
|
|
24
36
|
'case',
|
|
@@ -67,14 +79,12 @@ const RESERVED_IDENTIFIERS = new Set([
|
|
|
67
79
|
'static',
|
|
68
80
|
'yield'
|
|
69
81
|
]);
|
|
70
|
-
|
|
71
82
|
const toSafeIdentifier = (value) => {
|
|
72
83
|
const normalized = toCamelCase(value || 'repository');
|
|
73
84
|
if (!normalized.length) return 'repository';
|
|
74
85
|
if (RESERVED_IDENTIFIERS.has(normalized)) return `${normalized}Repository`;
|
|
75
86
|
return normalized;
|
|
76
87
|
};
|
|
77
|
-
|
|
78
88
|
export const getRepositoryMeta = (repositoryClassName) => {
|
|
79
89
|
const className = ensureRepositoryName(repositoryClassName);
|
|
80
90
|
const baseName = className.replace(/Repository$/, '') || 'Repository';
|
|
@@ -82,7 +92,6 @@ export const getRepositoryMeta = (repositoryClassName) => {
|
|
|
82
92
|
const repositoryInterfaceName = `I${baseName}Repository`;
|
|
83
93
|
const useCasesClassName = `${baseName}UseCases`;
|
|
84
94
|
const repositoryDependencyName = toSafeIdentifier(baseName || 'repository');
|
|
85
|
-
|
|
86
95
|
return {
|
|
87
96
|
className,
|
|
88
97
|
baseName,
|
|
@@ -92,25 +101,22 @@ export const getRepositoryMeta = (repositoryClassName) => {
|
|
|
92
101
|
repositoryDependencyName
|
|
93
102
|
};
|
|
94
103
|
};
|
|
95
|
-
|
|
96
104
|
export const getConfigState = async () => {
|
|
97
105
|
const config = await loadUserConfig();
|
|
98
106
|
const contexts = normalizeContexts(config.contexts);
|
|
99
|
-
const
|
|
100
|
-
|
|
107
|
+
const { sharedAlias, sharedContext } = await getSharedState();
|
|
101
108
|
return {
|
|
102
109
|
contexts,
|
|
103
|
-
|
|
110
|
+
sharedAlias,
|
|
111
|
+
sharedContextName: sharedContext.name
|
|
104
112
|
};
|
|
105
113
|
};
|
|
106
|
-
|
|
107
114
|
export const selectContext = async (message = 'Select context:') => {
|
|
108
115
|
const { contexts } = await getConfigState();
|
|
109
116
|
if (!contexts.length) {
|
|
110
117
|
console.error('❌ Cannot continue: `contexts` is missing or empty in azure-net.config.ts/js');
|
|
111
118
|
return null;
|
|
112
119
|
}
|
|
113
|
-
|
|
114
120
|
const { context } = await prompts({
|
|
115
121
|
type: 'select',
|
|
116
122
|
name: 'context',
|
|
@@ -118,30 +124,25 @@ export const selectContext = async (message = 'Select context:') => {
|
|
|
118
124
|
choices: contexts.map((item) => ({ title: item.name, value: item.name })),
|
|
119
125
|
initial: 0
|
|
120
126
|
});
|
|
121
|
-
|
|
122
127
|
return context ? String(context) : null;
|
|
123
128
|
};
|
|
124
|
-
|
|
125
129
|
export const resolveContextAlias = (contexts, contextName) => {
|
|
126
130
|
const found = contexts.find((item) => item.name === contextName);
|
|
127
131
|
if (!found) return `$${contextName}`;
|
|
128
132
|
return normalizeAlias(found.alias, `$${contextName}`);
|
|
129
133
|
};
|
|
130
|
-
|
|
131
134
|
export const resolveRepositoriesPath = (contextName) =>
|
|
132
135
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'infrastructure', 'http', 'repositories');
|
|
133
|
-
|
|
134
136
|
export const resolveDatasourcesPath = (contextName) =>
|
|
135
137
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'infrastructure', 'http', 'datasources');
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
export const resolveSharedDatasourcesPath = async () => {
|
|
139
|
+
const { sharedContext } = await getSharedState();
|
|
140
|
+
return getFoundationConstructPath(sharedContext.name, 'datasource');
|
|
141
|
+
};
|
|
139
142
|
export const resolveDomainRootPath = (contextName, domainName) =>
|
|
140
143
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'domain', domainName);
|
|
141
|
-
|
|
142
144
|
export const resolveUseCasesPath = (contextName) =>
|
|
143
145
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'application', 'use-cases');
|
|
144
|
-
|
|
145
146
|
export const getAvailableTsNames = async (targetPath) => {
|
|
146
147
|
try {
|
|
147
148
|
const files = await fs.readdir(targetPath);
|
|
@@ -150,25 +151,22 @@ export const getAvailableTsNames = async (targetPath) => {
|
|
|
150
151
|
return [];
|
|
151
152
|
}
|
|
152
153
|
};
|
|
153
|
-
|
|
154
154
|
export const selectDatasource = async (contextName) => {
|
|
155
|
-
const { contexts,
|
|
155
|
+
const { contexts, sharedAlias } = await getConfigState();
|
|
156
156
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
157
|
-
const
|
|
157
|
+
const sharedDatasources = await getAvailableTsNames(await resolveSharedDatasourcesPath());
|
|
158
158
|
const contextDatasources = await getAvailableTsNames(resolveDatasourcesPath(contextName));
|
|
159
|
-
|
|
160
159
|
const choices = [];
|
|
161
|
-
for (const datasource of
|
|
160
|
+
for (const datasource of sharedDatasources) {
|
|
162
161
|
choices.push({
|
|
163
|
-
title: `${datasource} (
|
|
162
|
+
title: `${datasource} (shared foundation)`,
|
|
164
163
|
value: {
|
|
165
164
|
name: datasource,
|
|
166
|
-
source: '
|
|
167
|
-
importPath:
|
|
165
|
+
source: 'shared',
|
|
166
|
+
importPath: getFoundationConstructImportPath(sharedAlias, 'datasource')
|
|
168
167
|
}
|
|
169
168
|
});
|
|
170
169
|
}
|
|
171
|
-
|
|
172
170
|
for (const datasource of contextDatasources) {
|
|
173
171
|
choices.push({
|
|
174
172
|
title: `${datasource} (${contextName})`,
|
|
@@ -179,12 +177,10 @@ export const selectDatasource = async (contextName) => {
|
|
|
179
177
|
}
|
|
180
178
|
});
|
|
181
179
|
}
|
|
182
|
-
|
|
183
180
|
if (!choices.length) {
|
|
184
|
-
console.error(`❌ No datasources found in
|
|
181
|
+
console.error(`❌ No datasources found in shared foundation or context "${contextName}".`);
|
|
185
182
|
return null;
|
|
186
183
|
}
|
|
187
|
-
|
|
188
184
|
const { datasource } = await prompts({
|
|
189
185
|
type: 'select',
|
|
190
186
|
name: 'datasource',
|
|
@@ -192,106 +188,86 @@ export const selectDatasource = async (contextName) => {
|
|
|
192
188
|
choices,
|
|
193
189
|
initial: 0
|
|
194
190
|
});
|
|
195
|
-
|
|
196
191
|
return datasource ?? null;
|
|
197
192
|
};
|
|
198
|
-
|
|
199
193
|
const updateDomainIndex = async (domainRootPath) => {
|
|
200
194
|
const exports = [];
|
|
201
|
-
|
|
202
195
|
try {
|
|
203
196
|
await fs.access(path.join(domainRootPath, 'ports'));
|
|
204
197
|
exports.push(`export * from './ports';`);
|
|
205
198
|
} catch {
|
|
206
199
|
// ignore
|
|
207
200
|
}
|
|
208
|
-
|
|
209
201
|
try {
|
|
210
202
|
await fs.access(path.join(domainRootPath, 'model'));
|
|
211
203
|
exports.push(`export * from './model';`);
|
|
212
204
|
} catch {
|
|
213
205
|
// ignore
|
|
214
206
|
}
|
|
215
|
-
|
|
216
207
|
const content = exports.length ? `${exports.join('\n')}\n` : '';
|
|
217
208
|
await fs.mkdir(domainRootPath, { recursive: true });
|
|
218
209
|
await fs.writeFile(path.join(domainRootPath, 'index.ts'), content, 'utf-8');
|
|
219
210
|
};
|
|
220
|
-
|
|
221
211
|
const normalizeTypeName = (rawTypeName) => {
|
|
222
212
|
const normalized = toPascalCase(String(rawTypeName ?? '').trim());
|
|
223
213
|
return normalized || 'IType';
|
|
224
214
|
};
|
|
225
|
-
|
|
226
215
|
export const ensureDomainType = async ({ contextName, domainName, typeName, layer }) => {
|
|
227
216
|
const interfaceName = normalizeTypeName(typeName);
|
|
228
217
|
const domainRootPath = resolveDomainRootPath(contextName, domainName);
|
|
229
218
|
const preferredLayer = layer === 'model' ? 'model' : 'ports';
|
|
230
219
|
const portsTypePath = path.join(domainRootPath, 'ports', `${interfaceName}.ts`);
|
|
231
220
|
const modelTypePath = path.join(domainRootPath, 'model', `${interfaceName}.ts`);
|
|
232
|
-
|
|
233
221
|
try {
|
|
234
222
|
await fs.access(portsTypePath);
|
|
235
223
|
return { interfaceName, layer: 'ports' };
|
|
236
224
|
} catch {
|
|
237
225
|
// ignore
|
|
238
226
|
}
|
|
239
|
-
|
|
240
227
|
try {
|
|
241
228
|
await fs.access(modelTypePath);
|
|
242
229
|
return { interfaceName, layer: 'model' };
|
|
243
230
|
} catch {
|
|
244
231
|
// ignore
|
|
245
232
|
}
|
|
246
|
-
|
|
247
233
|
const layerPath = path.join(domainRootPath, preferredLayer);
|
|
248
234
|
const filePath = path.join(layerPath, `${interfaceName}.ts`);
|
|
249
|
-
|
|
250
235
|
await fs.mkdir(layerPath, { recursive: true });
|
|
251
236
|
await writeIfNotExists(filePath, `export interface ${interfaceName} {\n\t[key: string]: unknown;\n}\n`);
|
|
252
237
|
await updateIndexTs(layerPath);
|
|
253
238
|
await updateDomainIndex(domainRootPath);
|
|
254
|
-
|
|
255
239
|
return { interfaceName, layer: preferredLayer };
|
|
256
240
|
};
|
|
257
|
-
|
|
258
241
|
export const ensureRepositoryInterfaceFile = async ({ contextName, meta, methods }) => {
|
|
259
242
|
const domainRootPath = resolveDomainRootPath(contextName, meta.domainName);
|
|
260
243
|
const portsPath = path.join(domainRootPath, 'ports');
|
|
261
244
|
await fs.mkdir(portsPath, { recursive: true });
|
|
262
|
-
|
|
263
245
|
const resolveTypeLayer = async (typeName) => {
|
|
264
246
|
const portsTypePath = path.join(domainRootPath, 'ports', `${typeName}.ts`);
|
|
265
247
|
const modelTypePath = path.join(domainRootPath, 'model', `${typeName}.ts`);
|
|
266
|
-
|
|
267
248
|
try {
|
|
268
249
|
await fs.access(portsTypePath);
|
|
269
250
|
return 'ports';
|
|
270
251
|
} catch {
|
|
271
252
|
// ignore
|
|
272
253
|
}
|
|
273
|
-
|
|
274
254
|
try {
|
|
275
255
|
await fs.access(modelTypePath);
|
|
276
256
|
return 'model';
|
|
277
257
|
} catch {
|
|
278
258
|
// ignore
|
|
279
259
|
}
|
|
280
|
-
|
|
281
260
|
return null;
|
|
282
261
|
};
|
|
283
|
-
|
|
284
262
|
const importsByLayer = { ports: new Set(), model: new Set() };
|
|
285
263
|
for (const method of methods) {
|
|
286
|
-
const relatedTypes = [method.responseType, method.requestType].filter(Boolean);
|
|
264
|
+
const relatedTypes = [method.responseType, method.requestType].filter((item) => Boolean(item));
|
|
287
265
|
for (const typeName of relatedTypes) {
|
|
288
|
-
// eslint-disable-next-line no-await-in-loop
|
|
289
266
|
const typeLayer = await resolveTypeLayer(typeName);
|
|
290
267
|
if (typeLayer === 'ports') importsByLayer.ports.add(typeName);
|
|
291
268
|
if (typeLayer === 'model') importsByLayer.model.add(typeName);
|
|
292
269
|
}
|
|
293
270
|
}
|
|
294
|
-
|
|
295
271
|
const importLines = [];
|
|
296
272
|
for (const typeName of Array.from(importsByLayer.ports)) {
|
|
297
273
|
importLines.push(`import type { ${typeName} } from './${typeName}.js';`);
|
|
@@ -299,56 +275,45 @@ export const ensureRepositoryInterfaceFile = async ({ contextName, meta, methods
|
|
|
299
275
|
for (const typeName of Array.from(importsByLayer.model)) {
|
|
300
276
|
importLines.push(`import type { ${typeName} } from '../model/${typeName}.js';`);
|
|
301
277
|
}
|
|
302
|
-
|
|
303
278
|
const signatures = methods
|
|
304
279
|
.map((method) => {
|
|
305
280
|
const arg = method.requestType ? `data: ${method.requestType}` : '';
|
|
306
281
|
return `\t${method.name}(${arg}): Promise<${method.responseType}>;`;
|
|
307
282
|
})
|
|
308
283
|
.join('\n');
|
|
309
|
-
|
|
310
284
|
const content = `${importLines.join('\n')}${importLines.length ? '\n\n' : ''}export interface ${meta.repositoryInterfaceName} {\n${signatures ? `${signatures}\n` : ''}}\n`;
|
|
311
285
|
await fs.writeFile(path.join(portsPath, `${meta.repositoryInterfaceName}.ts`), content, 'utf-8');
|
|
312
286
|
await updateIndexTs(portsPath);
|
|
313
287
|
await updateDomainIndex(domainRootPath);
|
|
314
288
|
};
|
|
315
|
-
|
|
316
289
|
const buildHttpCall = (method) => {
|
|
317
290
|
const httpMethod = method.httpMethod.toLowerCase();
|
|
318
291
|
const route = method.route || '/public-route';
|
|
319
292
|
if (!method.requestType) {
|
|
320
293
|
return `http.${httpMethod}('${route}')`;
|
|
321
294
|
}
|
|
322
|
-
|
|
323
295
|
if (httpMethod === 'get' || httpMethod === 'head' || httpMethod === 'options') {
|
|
324
296
|
return `http.${httpMethod}('${route}', { searchParams: query.toSearchParams(data) })`;
|
|
325
297
|
}
|
|
326
|
-
|
|
327
298
|
return `http.${httpMethod}('${route}', { json: data })`;
|
|
328
299
|
};
|
|
329
|
-
|
|
330
300
|
export const buildRepositoryMethodCode = (method) => {
|
|
331
301
|
const arg = method.requestType ? `data: ${method.requestType}` : '';
|
|
332
302
|
const call = buildHttpCall(method);
|
|
333
|
-
|
|
334
303
|
let getInCallback = `http`;
|
|
335
304
|
const httpMethod = String(method.httpMethod ?? '').toLowerCase();
|
|
336
305
|
if (httpMethod === 'get' || httpMethod === 'head' || httpMethod === 'options') {
|
|
337
306
|
getInCallback = `http, query`;
|
|
338
307
|
}
|
|
339
|
-
|
|
340
308
|
return `\tpublic async ${method.name}(${arg}) {\n\t\treturn this.datasource\n\t\t\t.createRequest<${method.responseType}>(({ ${getInCallback} }) => ${call})\n\t\t\t.then((res) => res.get());\n\t}`;
|
|
341
309
|
};
|
|
342
|
-
|
|
343
310
|
export const buildRepositoryContent = ({ meta, contextAlias, datasourceImportPath, datasourceName, methods }) => {
|
|
344
311
|
const importedTypes = new Set([meta.repositoryInterfaceName]);
|
|
345
312
|
for (const method of methods) {
|
|
346
313
|
importedTypes.add(method.responseType);
|
|
347
314
|
if (method.requestType) importedTypes.add(method.requestType);
|
|
348
315
|
}
|
|
349
|
-
|
|
350
316
|
const methodsCode = methods.map((method) => buildRepositoryMethodCode(method)).join('\n\n');
|
|
351
|
-
|
|
352
317
|
return `import { ${datasourceName} } from '${datasourceImportPath}';
|
|
353
318
|
import type { ${Array.from(importedTypes).join(', ')} } from '${contextAlias}/layers/domain/${meta.domainName}';
|
|
354
319
|
|
|
@@ -359,35 +324,28 @@ ${methodsCode ? `${methodsCode}\n` : ''}
|
|
|
359
324
|
}
|
|
360
325
|
`;
|
|
361
326
|
};
|
|
362
|
-
|
|
363
327
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
364
|
-
|
|
365
328
|
const upsertImport = (content, source, names) => {
|
|
366
|
-
const uniqueNames = Array.from(new Set(names)).filter(Boolean);
|
|
329
|
+
const uniqueNames = Array.from(new Set(names)).filter((item) => Boolean(item));
|
|
367
330
|
if (!uniqueNames.length) return content;
|
|
368
|
-
|
|
369
331
|
const importRegex = new RegExp(`import\\s+(type\\s+)?\\{([^}]+)\\}\\s*from\\s*['"]${escapeRegExp(source)}['"];?`);
|
|
370
332
|
const match = content.match(importRegex);
|
|
371
|
-
|
|
372
333
|
if (match) {
|
|
373
|
-
const existing = match[2]
|
|
334
|
+
const existing = (match[2] ?? '')
|
|
374
335
|
.split(',')
|
|
375
336
|
.map((part) => part.trim())
|
|
376
337
|
.filter(Boolean);
|
|
377
338
|
const merged = Array.from(new Set([...existing, ...uniqueNames]));
|
|
378
339
|
return content.replace(importRegex, `import type { ${merged.join(', ')} } from '${source}';`);
|
|
379
340
|
}
|
|
380
|
-
|
|
381
341
|
const lines = content.split('\n');
|
|
382
342
|
let insertIndex = 0;
|
|
383
|
-
while (insertIndex < lines.length && lines[insertIndex].startsWith('import ')) {
|
|
343
|
+
while (insertIndex < lines.length && (lines[insertIndex] ?? '').startsWith('import ')) {
|
|
384
344
|
insertIndex += 1;
|
|
385
345
|
}
|
|
386
|
-
|
|
387
346
|
lines.splice(insertIndex, 0, `import type { ${uniqueNames.join(', ')} } from '${source}';`);
|
|
388
347
|
return lines.join('\n');
|
|
389
348
|
};
|
|
390
|
-
|
|
391
349
|
export const appendRepositoryMethod = async ({ repositoryPath, method, contextAlias, domainName }) => {
|
|
392
350
|
let content = await fs.readFile(repositoryPath, 'utf-8');
|
|
393
351
|
const methodExistsRegex = new RegExp(`\\bpublic\\s+async\\s+${escapeRegExp(method.name)}\\s*\\(`);
|
|
@@ -395,53 +353,46 @@ export const appendRepositoryMethod = async ({ repositoryPath, method, contextAl
|
|
|
395
353
|
console.warn(`⚠️ Method "${method.name}" already exists in repository, skipped.`);
|
|
396
354
|
return false;
|
|
397
355
|
}
|
|
398
|
-
|
|
399
356
|
content = upsertImport(content, `${contextAlias}/layers/domain/${domainName}`, [method.responseType, method.requestType]);
|
|
400
|
-
|
|
401
357
|
const methodCode = buildRepositoryMethodCode(method);
|
|
402
358
|
const classEnd = content.lastIndexOf('\n}');
|
|
403
359
|
if (classEnd === -1) {
|
|
404
360
|
throw new Error(`Repository class end was not found in ${repositoryPath}`);
|
|
405
361
|
}
|
|
406
|
-
|
|
407
362
|
content = `${content.slice(0, classEnd)}\n\n${methodCode}\n${content.slice(classEnd)}`;
|
|
408
363
|
await fs.writeFile(repositoryPath, content, 'utf-8');
|
|
409
364
|
return true;
|
|
410
365
|
};
|
|
411
|
-
|
|
412
366
|
export const parseRepositoryMethods = (content) => {
|
|
413
367
|
const methods = [];
|
|
414
368
|
const regex =
|
|
415
369
|
/public\s+async\s+(\w+)\(([^)]*)\)\s*\{[\s\S]*?\.createRequest<([^>]+)>\(\(\{ http \}\) =>\s*http\.(get|post|put|delete|head|options|patch)\(/g;
|
|
416
|
-
|
|
417
370
|
let match;
|
|
418
371
|
while ((match = regex.exec(content)) !== null) {
|
|
419
372
|
const name = match[1];
|
|
420
373
|
const params = match[2] ?? '';
|
|
421
374
|
const responseType = (match[3] ?? '').trim();
|
|
422
|
-
const httpMethod = (match[4]
|
|
375
|
+
const httpMethod = toHttpMethod(match[4]);
|
|
423
376
|
const requestTypeMatch = params.match(/:\s*([A-Za-z0-9_]+)/);
|
|
424
377
|
const requestType = requestTypeMatch?.[1] ?? null;
|
|
425
|
-
|
|
426
378
|
methods.push({
|
|
427
|
-
name,
|
|
379
|
+
name: name ?? 'method',
|
|
428
380
|
httpMethod,
|
|
429
381
|
responseType,
|
|
382
|
+
responseLayer: 'ports',
|
|
430
383
|
requestType,
|
|
384
|
+
requestLayer: requestType ? 'ports' : null,
|
|
431
385
|
route: '/public-route'
|
|
432
386
|
});
|
|
433
387
|
}
|
|
434
|
-
|
|
435
388
|
return methods;
|
|
436
389
|
};
|
|
437
|
-
|
|
438
390
|
export const ensureUseCasesFile = async ({ contextName, contextAlias, meta }) => {
|
|
439
391
|
const repositoryInterfacePath = path.join(
|
|
440
392
|
resolveDomainRootPath(contextName, meta.domainName),
|
|
441
393
|
'ports',
|
|
442
394
|
`${meta.repositoryInterfaceName}.ts`
|
|
443
395
|
);
|
|
444
|
-
|
|
445
396
|
let interfaceContent = '';
|
|
446
397
|
try {
|
|
447
398
|
interfaceContent = await fs.readFile(repositoryInterfacePath, 'utf-8');
|
|
@@ -449,18 +400,16 @@ export const ensureUseCasesFile = async ({ contextName, contextAlias, meta }) =>
|
|
|
449
400
|
console.error(`❌ Repository interface not found: ${repositoryInterfacePath}`);
|
|
450
401
|
return false;
|
|
451
402
|
}
|
|
452
|
-
|
|
453
403
|
const methods = [];
|
|
454
404
|
const methodRegex = /(\w+)\(([^)]*)\)\s*:\s*Promise<[^>]+>;/g;
|
|
455
405
|
let methodMatch;
|
|
456
406
|
while ((methodMatch = methodRegex.exec(interfaceContent)) !== null) {
|
|
457
|
-
|
|
407
|
+
const methodName = methodMatch[1];
|
|
408
|
+
if (methodName) methods.push(methodName);
|
|
458
409
|
}
|
|
459
|
-
|
|
460
410
|
const declares = methods.map((methodName) => `\tdeclare ${methodName}: ${meta.repositoryInterfaceName}['${methodName}'];`).join('\n');
|
|
461
411
|
const useCasesPath = resolveUseCasesPath(contextName);
|
|
462
412
|
await fs.mkdir(useCasesPath, { recursive: true });
|
|
463
|
-
|
|
464
413
|
const content = `import { ClassMirror } from '@azure-net/kit';
|
|
465
414
|
import type { ${meta.repositoryInterfaceName} } from '${contextAlias}/layers/domain/${meta.domainName}';
|
|
466
415
|
|
|
@@ -472,12 +421,10 @@ export class ${meta.useCasesClassName} extends ClassMirror {
|
|
|
472
421
|
${declares ? `${declares}\n` : ''}
|
|
473
422
|
}
|
|
474
423
|
`;
|
|
475
|
-
|
|
476
424
|
await fs.writeFile(path.join(useCasesPath, `${meta.useCasesClassName}.ts`), content, 'utf-8');
|
|
477
425
|
await updateIndexTs(useCasesPath);
|
|
478
426
|
return true;
|
|
479
427
|
};
|
|
480
|
-
|
|
481
428
|
export const promptMethodDefinition = async () => {
|
|
482
429
|
const { methodNameRaw } = await prompts({
|
|
483
430
|
type: 'text',
|
|
@@ -485,7 +432,6 @@ export const promptMethodDefinition = async () => {
|
|
|
485
432
|
message: 'Method name:',
|
|
486
433
|
validate: (value) => (String(value ?? '').trim().length > 0 ? true : 'Method name is required')
|
|
487
434
|
});
|
|
488
|
-
|
|
489
435
|
const { httpMethod } = await prompts({
|
|
490
436
|
type: 'select',
|
|
491
437
|
name: 'httpMethod',
|
|
@@ -493,14 +439,12 @@ export const promptMethodDefinition = async () => {
|
|
|
493
439
|
choices: HTTP_METHODS.map((value) => ({ title: value, value })),
|
|
494
440
|
initial: 0
|
|
495
441
|
});
|
|
496
|
-
|
|
497
442
|
const { responseTypeRaw } = await prompts({
|
|
498
443
|
type: 'text',
|
|
499
444
|
name: 'responseTypeRaw',
|
|
500
445
|
message: 'Response type name:',
|
|
501
446
|
validate: (value) => (String(value ?? '').trim().length > 0 ? true : 'Response type is required')
|
|
502
447
|
});
|
|
503
|
-
|
|
504
448
|
const { responseLayer } = await prompts({
|
|
505
449
|
type: 'select',
|
|
506
450
|
name: 'responseLayer',
|
|
@@ -511,7 +455,6 @@ export const promptMethodDefinition = async () => {
|
|
|
511
455
|
],
|
|
512
456
|
initial: 0
|
|
513
457
|
});
|
|
514
|
-
|
|
515
458
|
const { routeRaw } = await prompts({
|
|
516
459
|
type: 'text',
|
|
517
460
|
name: 'routeRaw',
|
|
@@ -523,17 +466,14 @@ export const promptMethodDefinition = async () => {
|
|
|
523
466
|
return raw.startsWith('/') ? true : 'Path must start with "/"';
|
|
524
467
|
}
|
|
525
468
|
});
|
|
526
|
-
|
|
527
469
|
const { hasRequest } = await prompts({
|
|
528
470
|
type: 'confirm',
|
|
529
471
|
name: 'hasRequest',
|
|
530
472
|
message: 'Has request payload type?',
|
|
531
473
|
initial: false
|
|
532
474
|
});
|
|
533
|
-
|
|
534
475
|
let requestType = null;
|
|
535
476
|
let requestLayer = null;
|
|
536
|
-
|
|
537
477
|
if (hasRequest) {
|
|
538
478
|
const { requestTypeRaw } = await prompts({
|
|
539
479
|
type: 'text',
|
|
@@ -541,7 +481,6 @@ export const promptMethodDefinition = async () => {
|
|
|
541
481
|
message: 'Request type name:',
|
|
542
482
|
validate: (value) => (String(value ?? '').trim().length > 0 ? true : 'Request type is required')
|
|
543
483
|
});
|
|
544
|
-
|
|
545
484
|
const layerResult = await prompts({
|
|
546
485
|
type: 'select',
|
|
547
486
|
name: 'requestLayer',
|
|
@@ -552,14 +491,12 @@ export const promptMethodDefinition = async () => {
|
|
|
552
491
|
],
|
|
553
492
|
initial: 0
|
|
554
493
|
});
|
|
555
|
-
|
|
556
494
|
requestType = normalizeTypeName(requestTypeRaw);
|
|
557
495
|
requestLayer = layerResult.requestLayer ?? 'ports';
|
|
558
496
|
}
|
|
559
|
-
|
|
560
497
|
return {
|
|
561
498
|
name: toCamelCase(String(methodNameRaw ?? '').trim() || 'method'),
|
|
562
|
-
httpMethod:
|
|
499
|
+
httpMethod: toHttpMethod(httpMethod),
|
|
563
500
|
responseType: normalizeTypeName(responseTypeRaw),
|
|
564
501
|
responseLayer: responseLayer ?? 'ports',
|
|
565
502
|
route: String(routeRaw ?? '/public-route').trim() || '/public-route',
|
|
@@ -5,6 +5,7 @@ import { loadUserConfig } from '../../utils/loadConfig.js';
|
|
|
5
5
|
import { normalizeContexts } from '../../init/initAliases.js';
|
|
6
6
|
import { toCamelCase, toKebabCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
7
7
|
import { updateIndexTs, writeIfNotExists } 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 DomainLayer = 'ports' | 'model';
|
|
@@ -17,7 +18,7 @@ type RepositoryMeta = {
|
|
|
17
18
|
useCasesClassName: string;
|
|
18
19
|
repositoryDependencyName: string;
|
|
19
20
|
};
|
|
20
|
-
type DatasourceInfo = { name: string; source: '
|
|
21
|
+
type DatasourceInfo = { name: string; source: 'shared' | 'context'; importPath: string };
|
|
21
22
|
type RepositoryMethod = {
|
|
22
23
|
name: string;
|
|
23
24
|
httpMethod: HttpMethod;
|
|
@@ -131,14 +132,15 @@ export const getRepositoryMeta = (repositoryClassName: unknown): RepositoryMeta
|
|
|
131
132
|
};
|
|
132
133
|
};
|
|
133
134
|
|
|
134
|
-
export const getConfigState = async (): Promise<{ contexts: ContextConfig[];
|
|
135
|
+
export const getConfigState = async (): Promise<{ contexts: ContextConfig[]; sharedAlias: string; sharedContextName: string }> => {
|
|
135
136
|
const config = await loadUserConfig();
|
|
136
137
|
const contexts = normalizeContexts(config.contexts);
|
|
137
|
-
const
|
|
138
|
+
const { sharedAlias, sharedContext } = await getSharedState();
|
|
138
139
|
|
|
139
140
|
return {
|
|
140
141
|
contexts,
|
|
141
|
-
|
|
142
|
+
sharedAlias,
|
|
143
|
+
sharedContextName: sharedContext.name
|
|
142
144
|
};
|
|
143
145
|
};
|
|
144
146
|
|
|
@@ -172,7 +174,10 @@ export const resolveRepositoriesPath = (contextName: string): string =>
|
|
|
172
174
|
export const resolveDatasourcesPath = (contextName: string): string =>
|
|
173
175
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'infrastructure', 'http', 'datasources');
|
|
174
176
|
|
|
175
|
-
export const
|
|
177
|
+
export const resolveSharedDatasourcesPath = async (): Promise<string> => {
|
|
178
|
+
const { sharedContext } = await getSharedState();
|
|
179
|
+
return getFoundationConstructPath(sharedContext.name, 'datasource');
|
|
180
|
+
};
|
|
176
181
|
|
|
177
182
|
export const resolveDomainRootPath = (contextName: string, domainName: string): string =>
|
|
178
183
|
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'domain', domainName);
|
|
@@ -190,19 +195,19 @@ export const getAvailableTsNames = async (targetPath: string): Promise<string[]>
|
|
|
190
195
|
};
|
|
191
196
|
|
|
192
197
|
export const selectDatasource = async (contextName: string): Promise<DatasourceInfo | null> => {
|
|
193
|
-
const { contexts,
|
|
198
|
+
const { contexts, sharedAlias } = await getConfigState();
|
|
194
199
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
195
|
-
const
|
|
200
|
+
const sharedDatasources = await getAvailableTsNames(await resolveSharedDatasourcesPath());
|
|
196
201
|
const contextDatasources = await getAvailableTsNames(resolveDatasourcesPath(contextName));
|
|
197
202
|
|
|
198
203
|
const choices: Array<{ title: string; value: DatasourceInfo }> = [];
|
|
199
|
-
for (const datasource of
|
|
204
|
+
for (const datasource of sharedDatasources) {
|
|
200
205
|
choices.push({
|
|
201
|
-
title: `${datasource} (
|
|
206
|
+
title: `${datasource} (shared foundation)`,
|
|
202
207
|
value: {
|
|
203
208
|
name: datasource,
|
|
204
|
-
source: '
|
|
205
|
-
importPath:
|
|
209
|
+
source: 'shared',
|
|
210
|
+
importPath: getFoundationConstructImportPath(sharedAlias, 'datasource')
|
|
206
211
|
}
|
|
207
212
|
});
|
|
208
213
|
}
|
|
@@ -219,7 +224,7 @@ export const selectDatasource = async (contextName: string): Promise<DatasourceI
|
|
|
219
224
|
}
|
|
220
225
|
|
|
221
226
|
if (!choices.length) {
|
|
222
|
-
console.error(`❌ No datasources found in
|
|
227
|
+
console.error(`❌ No datasources found in shared foundation or context "${contextName}".`);
|
|
223
228
|
return null;
|
|
224
229
|
}
|
|
225
230
|
|