@azure-net/cli 5.0.0-3 → 5.0.1
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 +366 -371
- package/src/generate/module/generatePresenter.ts +76 -20
- 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 +67 -94
- package/src/scaffolds/createPresenterFactory.ts +7 -19
- 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,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$/, ''));
|
|
@@ -139,6 +142,7 @@ const isPrimitive = (typeName: string): boolean => PRIMITIVE_TYPES.has(String(ty
|
|
|
139
142
|
|
|
140
143
|
const normalizeTypeImports = (types: string[]): string[] => Array.from(new Set(types.filter((typeName) => !isPrimitive(typeName))));
|
|
141
144
|
const isPathParamName = (name: string): boolean => name === 'pathParam' || name === 'path';
|
|
145
|
+
const toTypeFileName = (typeName: string): string => String(typeName ?? '').replace(/^I/, '') || 'Type';
|
|
142
146
|
|
|
143
147
|
const buildMethodInvocation = (
|
|
144
148
|
method: RepositoryMethod & { httpMethod: string },
|
|
@@ -183,6 +187,46 @@ const getApplicationProviderFile = async (contextName: string): Promise<string |
|
|
|
183
187
|
}
|
|
184
188
|
};
|
|
185
189
|
|
|
190
|
+
const resolveDomainInterfaceFile = async ({
|
|
191
|
+
contextName,
|
|
192
|
+
domainName,
|
|
193
|
+
interfaceName
|
|
194
|
+
}: {
|
|
195
|
+
contextName: string;
|
|
196
|
+
domainName: string;
|
|
197
|
+
interfaceName: string;
|
|
198
|
+
}): Promise<string | null> => {
|
|
199
|
+
const portsPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'domain', domainName, 'ports');
|
|
200
|
+
const preferredPath = path.join(portsPath, `${toTypeFileName(interfaceName)}.ts`);
|
|
201
|
+
const legacyPath = path.join(portsPath, `${interfaceName}.ts`);
|
|
202
|
+
|
|
203
|
+
for (const candidate of [preferredPath, legacyPath]) {
|
|
204
|
+
try {
|
|
205
|
+
await fs.access(candidate);
|
|
206
|
+
return candidate;
|
|
207
|
+
} catch {
|
|
208
|
+
// Try the next candidate.
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const files = await fs.readdir(portsPath);
|
|
214
|
+
for (const fileName of files) {
|
|
215
|
+
if (!fileName.endsWith('.ts') || fileName === 'index.ts') continue;
|
|
216
|
+
const candidate = path.join(portsPath, fileName);
|
|
217
|
+
const content = await fs.readFile(candidate, 'utf-8');
|
|
218
|
+
const escapedName = interfaceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
219
|
+
if (new RegExp(`export\\s+interface\\s+${escapedName}\\b`).test(content)) {
|
|
220
|
+
return candidate;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
};
|
|
229
|
+
|
|
186
230
|
type GeneratePresenterOptions = {
|
|
187
231
|
contextName?: string;
|
|
188
232
|
useCasesName?: string;
|
|
@@ -260,17 +304,27 @@ export default async function generatePresenter(options: GeneratePresenterOption
|
|
|
260
304
|
}
|
|
261
305
|
|
|
262
306
|
const repositoryHttpMethods = parseRepositoryHttpMethods(repositoryInfo.content);
|
|
263
|
-
const domainPortsInterfacePath =
|
|
264
|
-
process.cwd(),
|
|
265
|
-
'src',
|
|
266
|
-
'app',
|
|
307
|
+
const domainPortsInterfacePath = await resolveDomainInterfaceFile({
|
|
267
308
|
contextName,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
309
|
+
domainName: useCasesInfo.domainName,
|
|
310
|
+
interfaceName: useCasesInfo.repositoryInterfaceName
|
|
311
|
+
});
|
|
312
|
+
if (!domainPortsInterfacePath) {
|
|
313
|
+
console.error(
|
|
314
|
+
`❌ Repository interface "${useCasesInfo.repositoryInterfaceName}" was not found in ${path.join(
|
|
315
|
+
process.cwd(),
|
|
316
|
+
'src',
|
|
317
|
+
'app',
|
|
318
|
+
contextName,
|
|
319
|
+
'layers',
|
|
320
|
+
'domain',
|
|
321
|
+
useCasesInfo.domainName,
|
|
322
|
+
'ports'
|
|
323
|
+
)}`
|
|
324
|
+
);
|
|
325
|
+
process.exitCode = 1;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
274
328
|
const repositoryInterfaceContent = await fs.readFile(domainPortsInterfacePath, 'utf-8');
|
|
275
329
|
const repositoryMethods = parseRepositoryInterfaceMethods(repositoryInterfaceContent).filter((method) =>
|
|
276
330
|
useCasesInfo.methods.includes(method.name)
|
|
@@ -287,13 +341,15 @@ export default async function generatePresenter(options: GeneratePresenterOption
|
|
|
287
341
|
const presentationPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'presentation', presenterFolder);
|
|
288
342
|
await fs.mkdir(presentationPath, { recursive: true });
|
|
289
343
|
|
|
290
|
-
const { contexts,
|
|
344
|
+
const { contexts, sharedAlias } = await getConfigState();
|
|
291
345
|
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
346
|
+
const presenterImportPath = getFoundationConstructImportPath(sharedAlias, 'presenter');
|
|
347
|
+
const schemaFactoryImportPath = getFoundationConstructImportPath(sharedAlias, 'schema');
|
|
292
348
|
|
|
293
349
|
let presenterFactory = String((await loadUserConfig()).defaultPresenterFactory ?? '').trim();
|
|
294
350
|
let useNativePresenter = false;
|
|
295
351
|
if (!presenterFactory) {
|
|
296
|
-
const availableFactories = await
|
|
352
|
+
const availableFactories = await getSharedPresenterFactories();
|
|
297
353
|
const { selectedFactory } = await prompts({
|
|
298
354
|
type: 'select',
|
|
299
355
|
name: 'selectedFactory',
|
|
@@ -345,9 +401,9 @@ export default async function generatePresenter(options: GeneratePresenterOption
|
|
|
345
401
|
|
|
346
402
|
if (!schemaFactoryResolved) {
|
|
347
403
|
if (!schemaFactory) {
|
|
348
|
-
const availableSchemas = await
|
|
404
|
+
const availableSchemas = await getSharedSchemaFactories();
|
|
349
405
|
if (!availableSchemas.length) {
|
|
350
|
-
console.error('❌ No schema factories found in
|
|
406
|
+
console.error('❌ No schema factories found in shared foundation constructs/schema.');
|
|
351
407
|
process.exitCode = 1;
|
|
352
408
|
return;
|
|
353
409
|
}
|
|
@@ -372,7 +428,7 @@ export default async function generatePresenter(options: GeneratePresenterOption
|
|
|
372
428
|
await fs.mkdir(schemaPath, { recursive: true });
|
|
373
429
|
schemaIndexNeeded = true;
|
|
374
430
|
|
|
375
|
-
const schemaContent = `import { ${schemaFactory} } from '${
|
|
431
|
+
const schemaContent = `import { ${schemaFactory} } from '${schemaFactoryImportPath}';
|
|
376
432
|
import type { ${requestParam.type} } from '${contextAlias}/layers/domain/${useCasesInfo.domainName}';
|
|
377
433
|
|
|
378
434
|
export const ${schemaConstName} = ${schemaFactory}<${requestParam.type}>()
|
|
@@ -414,7 +470,7 @@ export const ${schemaConstName} = ${schemaFactory}<${requestParam.type}>()
|
|
|
414
470
|
|
|
415
471
|
const factoryImport = useNativePresenter
|
|
416
472
|
? `import { createPresenter } from '@azure-net/kit/delivery';`
|
|
417
|
-
: `import { ${presenterFactory} } from '${
|
|
473
|
+
: `import { ${presenterFactory} } from '${presenterImportPath}';`;
|
|
418
474
|
|
|
419
475
|
const schemaImportLine = schemaImports.length ? `import { ${Array.from(new Set(schemaImports)).join(', ')} } from './schema';` : '';
|
|
420
476
|
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,
|
|
@@ -2,7 +2,6 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import prompts from 'prompts';
|
|
4
4
|
import { getConfigState, resolveContextAlias, selectContext } from './repositoryModuleShared.js';
|
|
5
|
-
|
|
6
5
|
const listDirectories = async (targetPath) => {
|
|
7
6
|
try {
|
|
8
7
|
const items = await fs.readdir(targetPath, { withFileTypes: true });
|
|
@@ -11,7 +10,6 @@ const listDirectories = async (targetPath) => {
|
|
|
11
10
|
return [];
|
|
12
11
|
}
|
|
13
12
|
};
|
|
14
|
-
|
|
15
13
|
const listSchemaFiles = async (targetPath) => {
|
|
16
14
|
try {
|
|
17
15
|
const files = await fs.readdir(targetPath);
|
|
@@ -20,24 +18,18 @@ const listSchemaFiles = async (targetPath) => {
|
|
|
20
18
|
return [];
|
|
21
19
|
}
|
|
22
20
|
};
|
|
23
|
-
|
|
24
21
|
const resolveAliasMap = async () => {
|
|
25
|
-
const { contexts
|
|
22
|
+
const { contexts } = await getConfigState();
|
|
26
23
|
const map = new Map();
|
|
27
|
-
map.set(coreAlias, path.join(process.cwd(), 'src', 'core'));
|
|
28
|
-
|
|
29
24
|
for (const context of contexts) {
|
|
30
25
|
const alias = resolveContextAlias(contexts, context.name);
|
|
31
26
|
map.set(alias, path.join(process.cwd(), 'src', 'app', context.name));
|
|
32
27
|
}
|
|
33
|
-
|
|
34
28
|
return map;
|
|
35
29
|
};
|
|
36
|
-
|
|
37
30
|
const resolveImportToFile = async (fromFile, importPath, aliasMap) => {
|
|
38
31
|
const rawImport = String(importPath ?? '').trim();
|
|
39
32
|
if (!rawImport.length) return null;
|
|
40
|
-
|
|
41
33
|
let absolute = '';
|
|
42
34
|
if (rawImport.startsWith('.')) {
|
|
43
35
|
absolute = path.resolve(path.dirname(fromFile), rawImport);
|
|
@@ -47,7 +39,6 @@ const resolveImportToFile = async (fromFile, importPath, aliasMap) => {
|
|
|
47
39
|
const suffix = rawImport === alias ? '' : rawImport.slice(alias.length + 1);
|
|
48
40
|
absolute = path.join(aliasMap.get(alias), suffix);
|
|
49
41
|
}
|
|
50
|
-
|
|
51
42
|
const tsCandidate = absolute.endsWith('.ts') ? absolute : `${absolute}.ts`;
|
|
52
43
|
try {
|
|
53
44
|
await fs.access(tsCandidate);
|
|
@@ -62,36 +53,34 @@ const resolveImportToFile = async (fromFile, importPath, aliasMap) => {
|
|
|
62
53
|
}
|
|
63
54
|
}
|
|
64
55
|
};
|
|
65
|
-
|
|
66
56
|
const extractSchemaTypeName = (content) => {
|
|
67
57
|
const typeMatch = content.match(/=\s*[A-Za-z0-9_]+<\s*([^>]+?)\s*>\s*\(\)/m);
|
|
68
58
|
if (!typeMatch) return null;
|
|
69
59
|
return String(typeMatch[1]).trim();
|
|
70
60
|
};
|
|
71
|
-
|
|
72
61
|
const findTypeImportPath = (content, typeName) => {
|
|
73
62
|
const importRegex = /import\s+type\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
|
|
74
63
|
let match;
|
|
75
64
|
while ((match = importRegex.exec(content)) !== null) {
|
|
76
|
-
const
|
|
65
|
+
const importedList = match[1];
|
|
66
|
+
const sourcePath = match[2];
|
|
67
|
+
if (!importedList || !sourcePath) continue;
|
|
68
|
+
const imported = importedList
|
|
77
69
|
.split(',')
|
|
78
70
|
.map((item) => item.trim())
|
|
79
71
|
.filter(Boolean);
|
|
80
72
|
if (imported.includes(typeName)) {
|
|
81
|
-
return
|
|
73
|
+
return sourcePath;
|
|
82
74
|
}
|
|
83
75
|
}
|
|
84
76
|
return null;
|
|
85
77
|
};
|
|
86
|
-
|
|
87
78
|
const extractInterfaceBody = (content, interfaceName) => {
|
|
88
79
|
const declarationRegex = new RegExp(`export\\s+interface\\s+${interfaceName}\\s*\\{`);
|
|
89
80
|
const declarationMatch = content.match(declarationRegex);
|
|
90
81
|
if (!declarationMatch || declarationMatch.index === undefined) return null;
|
|
91
|
-
|
|
92
82
|
const openBraceIndex = content.indexOf('{', declarationMatch.index);
|
|
93
83
|
if (openBraceIndex === -1) return null;
|
|
94
|
-
|
|
95
84
|
let depth = 0;
|
|
96
85
|
let closeBraceIndex = -1;
|
|
97
86
|
for (let index = openBraceIndex; index < content.length; index += 1) {
|
|
@@ -105,11 +94,9 @@ const extractInterfaceBody = (content, interfaceName) => {
|
|
|
105
94
|
}
|
|
106
95
|
}
|
|
107
96
|
}
|
|
108
|
-
|
|
109
97
|
if (closeBraceIndex === -1) return null;
|
|
110
98
|
return content.slice(openBraceIndex + 1, closeBraceIndex);
|
|
111
99
|
};
|
|
112
|
-
|
|
113
100
|
const resolveRelativeExportPath = async (fromFile, exportPath) => {
|
|
114
101
|
const absolute = path.resolve(path.dirname(fromFile), exportPath);
|
|
115
102
|
const tsCandidate = absolute.endsWith('.ts') ? absolute : `${absolute}.ts`;
|
|
@@ -126,38 +113,30 @@ const resolveRelativeExportPath = async (fromFile, exportPath) => {
|
|
|
126
113
|
}
|
|
127
114
|
}
|
|
128
115
|
};
|
|
129
|
-
|
|
130
116
|
const findInterfaceBodyByExportGraph = async (entryFilePath, interfaceName, visited = new Set()) => {
|
|
131
117
|
const resolvedPath = path.resolve(entryFilePath);
|
|
132
118
|
if (visited.has(resolvedPath)) return null;
|
|
133
119
|
visited.add(resolvedPath);
|
|
134
|
-
|
|
135
120
|
let content = '';
|
|
136
121
|
try {
|
|
137
122
|
content = await fs.readFile(resolvedPath, 'utf-8');
|
|
138
123
|
} catch {
|
|
139
124
|
return null;
|
|
140
125
|
}
|
|
141
|
-
|
|
142
126
|
const direct = extractInterfaceBody(content, interfaceName);
|
|
143
127
|
if (direct) return direct;
|
|
144
|
-
|
|
145
|
-
const exportRegex = /export\\s+(?:\\*|\\{[^}]+\\})\\s+from\\s+['\"]([^'\"]+)['\"]/g;
|
|
128
|
+
const exportRegex = /export\s+(?:\*|\{[^}]+\})\s+from\s+['"]([^'"]+)['"]/g;
|
|
146
129
|
let match;
|
|
147
130
|
while ((match = exportRegex.exec(content)) !== null) {
|
|
148
131
|
const exportPath = match[1];
|
|
149
132
|
if (!exportPath || !exportPath.startsWith('.')) continue;
|
|
150
|
-
// eslint-disable-next-line no-await-in-loop
|
|
151
133
|
const nextPath = await resolveRelativeExportPath(resolvedPath, exportPath);
|
|
152
134
|
if (!nextPath) continue;
|
|
153
|
-
// eslint-disable-next-line no-await-in-loop
|
|
154
135
|
const nested = await findInterfaceBodyByExportGraph(nextPath, interfaceName, visited);
|
|
155
136
|
if (nested) return nested;
|
|
156
137
|
}
|
|
157
|
-
|
|
158
138
|
return null;
|
|
159
139
|
};
|
|
160
|
-
|
|
161
140
|
const findInterfaceBodyByScan = async (rootDir, interfaceName) => {
|
|
162
141
|
const queue = [rootDir];
|
|
163
142
|
while (queue.length) {
|
|
@@ -169,7 +148,6 @@ const findInterfaceBodyByScan = async (rootDir, interfaceName) => {
|
|
|
169
148
|
} catch {
|
|
170
149
|
continue;
|
|
171
150
|
}
|
|
172
|
-
|
|
173
151
|
for (const entry of entries) {
|
|
174
152
|
const absolute = path.join(current, entry.name);
|
|
175
153
|
if (entry.isDirectory()) {
|
|
@@ -177,7 +155,6 @@ const findInterfaceBodyByScan = async (rootDir, interfaceName) => {
|
|
|
177
155
|
continue;
|
|
178
156
|
}
|
|
179
157
|
if (!entry.isFile() || !entry.name.endsWith('.ts')) continue;
|
|
180
|
-
// eslint-disable-next-line no-await-in-loop
|
|
181
158
|
const content = await fs.readFile(absolute, 'utf-8');
|
|
182
159
|
const body = extractInterfaceBody(content, interfaceName);
|
|
183
160
|
if (body) return body;
|
|
@@ -185,17 +162,16 @@ const findInterfaceBodyByScan = async (rootDir, interfaceName) => {
|
|
|
185
162
|
}
|
|
186
163
|
return null;
|
|
187
164
|
};
|
|
188
|
-
|
|
189
165
|
const extractRuleKeys = (body) => {
|
|
190
166
|
const keys = [];
|
|
191
167
|
const keyRegex = /^\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*:/gm;
|
|
192
168
|
let match;
|
|
193
169
|
while ((match = keyRegex.exec(body)) !== null) {
|
|
194
|
-
|
|
170
|
+
const key = match[1];
|
|
171
|
+
if (key) keys.push(key);
|
|
195
172
|
}
|
|
196
173
|
return Array.from(new Set(keys));
|
|
197
174
|
};
|
|
198
|
-
|
|
199
175
|
const extractInterfaceFields = (body) => {
|
|
200
176
|
const fields = [];
|
|
201
177
|
const fieldRegex = /^\s*(?:readonly\s+)?([A-Za-z_$][A-Za-z0-9_$]*)(\?)?\s*:\s*([^;]+);/gm;
|
|
@@ -211,53 +187,42 @@ const extractInterfaceFields = (body) => {
|
|
|
211
187
|
type
|
|
212
188
|
});
|
|
213
189
|
}
|
|
214
|
-
|
|
215
190
|
const unique = new Map();
|
|
216
191
|
for (const field of fields) {
|
|
217
192
|
if (!unique.has(field.name)) unique.set(field.name, field);
|
|
218
193
|
}
|
|
219
194
|
return Array.from(unique.values());
|
|
220
195
|
};
|
|
221
|
-
|
|
222
196
|
const buildRulesArray = (field) => {
|
|
223
197
|
const ruleCalls = [];
|
|
224
198
|
if (!field.optional) {
|
|
225
199
|
ruleCalls.push('rules.required()');
|
|
226
200
|
}
|
|
227
|
-
|
|
228
201
|
if (field.type === 'string') {
|
|
229
202
|
ruleCalls.push('rules.string()');
|
|
230
203
|
} else if (field.type === 'number') {
|
|
231
204
|
ruleCalls.push('rules.number()');
|
|
232
205
|
}
|
|
233
|
-
|
|
234
206
|
return `[${ruleCalls.join(', ')}]`;
|
|
235
207
|
};
|
|
236
|
-
|
|
237
208
|
const updateRulesObject = (schemaContent, fields) => {
|
|
238
209
|
const rulesRegex = /\.rules\(\(rules\)\s*=>\s*\(\{([\s\S]*?)\}\)\)/m;
|
|
239
210
|
const match = schemaContent.match(rulesRegex);
|
|
240
211
|
if (!match) return null;
|
|
241
|
-
|
|
242
212
|
const currentBody = match[1] ?? '';
|
|
243
213
|
const existingKeys = new Set(extractRuleKeys(currentBody));
|
|
244
214
|
const newLines = fields.filter((field) => !existingKeys.has(field.name)).map((field) => `\t\t${field.name}: ${buildRulesArray(field)},`);
|
|
245
|
-
|
|
246
215
|
if (!newLines.length) return schemaContent;
|
|
247
|
-
|
|
248
216
|
const trimmedBody = currentBody.trimEnd();
|
|
249
217
|
const nextBody = trimmedBody.length ? `${trimmedBody}\n${newLines.join('\n')}\n\t` : `\n${newLines.join('\n')}\n\t`;
|
|
250
|
-
|
|
251
218
|
return schemaContent.replace(rulesRegex, `.rules((rules) => ({${nextBody}}))`);
|
|
252
219
|
};
|
|
253
|
-
|
|
254
220
|
export default async function generateSchemaFromType() {
|
|
255
221
|
const contextName = await selectContext('Select context for schema update:');
|
|
256
222
|
if (!contextName) {
|
|
257
223
|
process.exitCode = 1;
|
|
258
224
|
return;
|
|
259
225
|
}
|
|
260
|
-
|
|
261
226
|
const presentationPath = path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'presentation');
|
|
262
227
|
const presenterFolders = await listDirectories(presentationPath);
|
|
263
228
|
if (!presenterFolders.length) {
|
|
@@ -265,7 +230,6 @@ export default async function generateSchemaFromType() {
|
|
|
265
230
|
process.exitCode = 1;
|
|
266
231
|
return;
|
|
267
232
|
}
|
|
268
|
-
|
|
269
233
|
const { presenterFolder } = await prompts({
|
|
270
234
|
type: 'select',
|
|
271
235
|
name: 'presenterFolder',
|
|
@@ -273,12 +237,10 @@ export default async function generateSchemaFromType() {
|
|
|
273
237
|
choices: presenterFolders.map((item) => ({ title: item, value: item })),
|
|
274
238
|
initial: 0
|
|
275
239
|
});
|
|
276
|
-
|
|
277
240
|
if (!presenterFolder) {
|
|
278
241
|
process.exitCode = 1;
|
|
279
242
|
return;
|
|
280
243
|
}
|
|
281
|
-
|
|
282
244
|
const schemaFolderPath = path.join(presentationPath, String(presenterFolder), 'schema');
|
|
283
245
|
const schemas = await listSchemaFiles(schemaFolderPath);
|
|
284
246
|
if (!schemas.length) {
|
|
@@ -286,7 +248,6 @@ export default async function generateSchemaFromType() {
|
|
|
286
248
|
process.exitCode = 1;
|
|
287
249
|
return;
|
|
288
250
|
}
|
|
289
|
-
|
|
290
251
|
const { schemaFile } = await prompts({
|
|
291
252
|
type: 'select',
|
|
292
253
|
name: 'schemaFile',
|
|
@@ -294,29 +255,24 @@ export default async function generateSchemaFromType() {
|
|
|
294
255
|
choices: schemas.map((item) => ({ title: item, value: item })),
|
|
295
256
|
initial: 0
|
|
296
257
|
});
|
|
297
|
-
|
|
298
258
|
if (!schemaFile) {
|
|
299
259
|
process.exitCode = 1;
|
|
300
260
|
return;
|
|
301
261
|
}
|
|
302
|
-
|
|
303
262
|
const schemaPath = path.join(schemaFolderPath, String(schemaFile));
|
|
304
263
|
const schemaContent = await fs.readFile(schemaPath, 'utf-8');
|
|
305
|
-
|
|
306
264
|
const typeName = extractSchemaTypeName(schemaContent);
|
|
307
265
|
if (!typeName) {
|
|
308
266
|
console.error('❌ Unable to resolve schema generic type.');
|
|
309
267
|
process.exitCode = 1;
|
|
310
268
|
return;
|
|
311
269
|
}
|
|
312
|
-
|
|
313
270
|
const importPath = findTypeImportPath(schemaContent, typeName);
|
|
314
271
|
if (!importPath) {
|
|
315
272
|
console.error(`❌ Unable to find import for type "${typeName}".`);
|
|
316
273
|
process.exitCode = 1;
|
|
317
274
|
return;
|
|
318
275
|
}
|
|
319
|
-
|
|
320
276
|
const aliasMap = await resolveAliasMap();
|
|
321
277
|
const typeFilePath = await resolveImportToFile(schemaPath, importPath, aliasMap);
|
|
322
278
|
if (!typeFilePath) {
|
|
@@ -324,7 +280,6 @@ export default async function generateSchemaFromType() {
|
|
|
324
280
|
process.exitCode = 1;
|
|
325
281
|
return;
|
|
326
282
|
}
|
|
327
|
-
|
|
328
283
|
let interfaceBody = await findInterfaceBodyByExportGraph(typeFilePath, typeName);
|
|
329
284
|
if (!interfaceBody) {
|
|
330
285
|
interfaceBody = await findInterfaceBodyByScan(path.dirname(typeFilePath), typeName);
|
|
@@ -334,25 +289,21 @@ export default async function generateSchemaFromType() {
|
|
|
334
289
|
process.exitCode = 1;
|
|
335
290
|
return;
|
|
336
291
|
}
|
|
337
|
-
|
|
338
292
|
const interfaceFields = extractInterfaceFields(interfaceBody);
|
|
339
293
|
if (!interfaceFields.length) {
|
|
340
294
|
console.log('ℹ️ Interface has no explicit keys to transfer into rules.');
|
|
341
295
|
return;
|
|
342
296
|
}
|
|
343
|
-
|
|
344
297
|
const updatedSchemaContent = updateRulesObject(schemaContent, interfaceFields);
|
|
345
298
|
if (!updatedSchemaContent) {
|
|
346
299
|
console.error('❌ Could not locate `.rules((rules) => ({}))` block in schema file.');
|
|
347
300
|
process.exitCode = 1;
|
|
348
301
|
return;
|
|
349
302
|
}
|
|
350
|
-
|
|
351
303
|
if (updatedSchemaContent === schemaContent) {
|
|
352
304
|
console.log('ℹ️ Rules already include all keys from type.');
|
|
353
305
|
return;
|
|
354
306
|
}
|
|
355
|
-
|
|
356
307
|
await fs.writeFile(schemaPath, updatedSchemaContent, 'utf-8');
|
|
357
308
|
console.log(`✅ Schema updated from type: ${schemaPath}`);
|
|
358
309
|
}
|
|
@@ -22,9 +22,8 @@ const listSchemaFiles = async (targetPath: string): Promise<string[]> => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const resolveAliasMap = async (): Promise<Map<string, string>> => {
|
|
25
|
-
const { contexts
|
|
25
|
+
const { contexts } = await getConfigState();
|
|
26
26
|
const map = new Map<string, string>();
|
|
27
|
-
map.set(coreAlias, path.join(process.cwd(), 'src', 'core'));
|
|
28
27
|
|
|
29
28
|
for (const context of contexts) {
|
|
30
29
|
const alias = resolveContextAlias(contexts, context.name);
|