@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.
Files changed (67) hide show
  1. package/bin/azure-net.js +5 -46
  2. package/bin/azure-net.ts +5 -5
  3. package/package.json +1 -1
  4. package/src/checks/checkDomain.js +8 -38
  5. package/src/checks/checkFoldersStructure.js +0 -14
  6. package/src/checks/checkHooks.js +0 -5
  7. package/src/checks/checkImports.js +0 -1
  8. package/src/checks/checkInternal.js +0 -1
  9. package/src/checks/checkLayerBoundaries.js +50 -54
  10. package/src/checks/checkLayerBoundaries.ts +55 -58
  11. package/src/checks/checkPresenterNames.js +1 -37
  12. package/src/checks/checkPresenterNames.ts +1 -6
  13. package/src/checks/checkProject.js +0 -6
  14. package/src/checks/checkProviderGraph.js +1 -38
  15. package/src/checks/checkProviderNames.js +0 -28
  16. package/src/checks/index.js +0 -1
  17. package/src/generate/layers/generateRepository.js +4 -19
  18. package/src/generate/layers/generateRepositoryMethod.js +0 -12
  19. package/src/generate/layers/generateUseCases.js +0 -6
  20. package/src/generate/layers/repositoryShared.js +41 -104
  21. package/src/generate/layers/repositoryShared.ts +17 -12
  22. package/src/generate/module/generateDomain.js +0 -25
  23. package/src/generate/module/generateModule.js +0 -10
  24. package/src/generate/module/generateModulePreset.js +10 -51
  25. package/src/generate/module/generateModulePreset.ts +9 -8
  26. package/src/generate/module/generatePresenter.js +366 -371
  27. package/src/generate/module/generatePresenter.ts +76 -20
  28. package/src/generate/module/generateRepository.js +0 -17
  29. package/src/generate/module/generateSchemaFromType.js +9 -58
  30. package/src/generate/module/generateSchemaFromType.ts +1 -2
  31. package/src/generate/module/generateTypesJson.js +4 -45
  32. package/src/generate/module/generateUseCases.js +0 -9
  33. package/src/generate/module/repositoryModuleShared.js +21 -98
  34. package/src/generate/module/repositoryModuleShared.ts +14 -10
  35. package/src/generate/ui/generateComponent.js +0 -16
  36. package/src/generate/ui/generateDesignComponent.js +0 -12
  37. package/src/generate/ui/generateWidget.js +0 -12
  38. package/src/init/initAliases.js +42 -64
  39. package/src/init/initAliases.ts +47 -42
  40. package/src/init/initEdges.js +0 -15
  41. package/src/init/initStructure.js +48 -21
  42. package/src/init/initStructure.ts +51 -3
  43. package/src/install/installFresh.js +0 -16
  44. package/src/plugins/addLint.js +11 -46
  45. package/src/scaffolds/createCoreSchema.js +6 -40
  46. package/src/scaffolds/createCoreSchema.ts +6 -18
  47. package/src/scaffolds/createDatasource.js +26 -61
  48. package/src/scaffolds/createDatasource.ts +26 -40
  49. package/src/scaffolds/createDatasourceProvider.js +19 -56
  50. package/src/scaffolds/createDatasourceProvider.ts +20 -34
  51. package/src/scaffolds/createPresenterFactory.js +67 -94
  52. package/src/scaffolds/createPresenterFactory.ts +7 -19
  53. package/src/scaffolds/createProgram.js +1 -5
  54. package/src/scaffolds/createResponse.js +3 -11
  55. package/src/scaffolds/createResponse.ts +3 -1
  56. package/src/scaffolds/createSchemaRule.js +3 -8
  57. package/src/scaffolds/createSchemaRule.ts +3 -1
  58. package/src/scaffolds/createTranslationManager.js +3 -14
  59. package/src/scaffolds/createTranslationManager.ts +3 -1
  60. package/src/utils/contextUtils.js +5 -20
  61. package/src/utils/contextUtils.ts +2 -3
  62. package/src/utils/fileUtils.js +3 -38
  63. package/src/utils/fileUtils.ts +0 -26
  64. package/src/utils/loadConfig.js +1 -14
  65. package/src/utils/loadConfig.ts +1 -3
  66. package/src/utils/sharedFoundation.js +48 -0
  67. 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 getCorePresenterFactories = async (): Promise<string[]> => {
112
- const presenterPath = path.join(process.cwd(), 'src', 'core', 'presenter');
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 getCoreSchemaFactories = async (): Promise<string[]> => {
122
- const schemaPath = path.join(process.cwd(), 'src', 'core', 'schema');
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 = path.join(
264
- process.cwd(),
265
- 'src',
266
- 'app',
307
+ const domainPortsInterfacePath = await resolveDomainInterfaceFile({
267
308
  contextName,
268
- 'layers',
269
- 'domain',
270
- useCasesInfo.domainName,
271
- 'ports',
272
- `${useCasesInfo.repositoryInterfaceName}.ts`
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, coreAlias } = await getConfigState();
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 getCorePresenterFactories();
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 getCoreSchemaFactories();
404
+ const availableSchemas = await getSharedSchemaFactories();
349
405
  if (!availableSchemas.length) {
350
- console.error('❌ No schema factories found in src/core/schema.');
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 '${coreAlias}/schema';
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 '${coreAlias}/presenter';`;
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, coreAlias } = await getConfigState();
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 imported = match[1]
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 match[2];
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
- keys.push(match[1]);
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, coreAlias } = await getConfigState();
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);