@azure-net/cli 5.1.1 → 6.0.0-next.2
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/README.md +189 -0
- package/bin/azure-net.js +236 -221
- package/dist/bin/azure-net.d.ts +2 -0
- package/dist/src/checks/architecture.d.ts +20 -0
- package/dist/src/checks/checkDomain.d.ts +1 -0
- package/dist/src/checks/checkFoldersStructure.d.ts +1 -0
- package/dist/src/checks/checkHooks.d.ts +1 -0
- package/dist/src/checks/checkImports.d.ts +1 -0
- package/dist/src/checks/checkInternal.d.ts +1 -0
- package/dist/src/checks/checkLayerBoundaries.d.ts +1 -0
- package/dist/src/checks/checkLayerImports.d.ts +1 -0
- package/dist/src/checks/checkPresenterNames.d.ts +1 -0
- package/dist/src/checks/checkProject.d.ts +1 -0
- package/dist/src/checks/checkProviderGraph.d.ts +1 -0
- package/dist/src/checks/checkProviderNames.d.ts +1 -0
- package/dist/src/generate/layers/generateRepository.d.ts +1 -0
- package/dist/src/generate/layers/generateRepositoryMethod.d.ts +1 -0
- package/dist/src/generate/layers/generateUseCases.d.ts +1 -0
- package/dist/src/generate/module/generateDomain.d.ts +8 -0
- package/dist/src/generate/module/generateModule.d.ts +1 -0
- package/dist/src/generate/module/generateModulePreset.d.ts +1 -0
- package/dist/src/generate/module/generatePresenter.d.ts +29 -0
- package/dist/src/generate/module/generateRepository.d.ts +15 -0
- package/dist/src/generate/module/generateRepositoryMethod.d.ts +2 -0
- package/dist/src/generate/module/generateSchemaFromType.d.ts +9 -0
- package/dist/src/generate/module/generateStore.d.ts +1 -0
- package/dist/src/generate/module/generateTypesJson.d.ts +2 -0
- package/dist/src/generate/module/generateUseCases.d.ts +1 -0
- package/dist/src/generate/module/repositoryModuleShared.d.ts +89 -0
- package/dist/src/generate/routes/generateRoutes.d.ts +14 -0
- package/dist/src/generate/ui/generateComponent.d.ts +1 -0
- package/dist/src/generate/ui/generateDesignComponent.d.ts +1 -0
- package/dist/src/generate/ui/generateWidget.d.ts +1 -0
- package/dist/src/git/commit.d.ts +23 -0
- package/dist/src/git/push.d.ts +11 -0
- package/dist/src/git/runGit.d.ts +2 -0
- package/dist/src/init/initAliases.d.ts +12 -0
- package/dist/src/init/initEdges.d.ts +2 -0
- package/dist/src/init/initStructure.d.ts +1 -0
- package/dist/src/install/configureCommitTools.d.ts +1 -0
- package/dist/src/install/installFresh.d.ts +1 -0
- package/dist/src/migrate/migrate.d.ts +22 -0
- package/dist/src/migrate/presenters.d.ts +7 -0
- package/dist/src/plugins/addLint.d.ts +5 -0
- package/dist/src/scaffolds/createCoreSchema.d.ts +1 -0
- package/dist/src/scaffolds/createDatasource.d.ts +1 -0
- package/dist/src/scaffolds/createProgram.d.ts +1 -0
- package/dist/src/scaffolds/createResponse.d.ts +1 -0
- package/dist/src/scaffolds/createSchemaRule.d.ts +1 -0
- package/dist/src/scaffolds/createTranslationManager.d.ts +1 -0
- package/{src/scaffolds/index.ts → dist/src/scaffolds/index.d.ts} +0 -2
- package/dist/src/scaffolds/registerDatasource.d.ts +2 -0
- package/dist/src/scaffolds/updateDefaultFactoryConfig.d.ts +1 -0
- package/dist/src/utils/architecture.d.ts +31 -0
- package/dist/src/utils/contextUtils.d.ts +6 -0
- package/dist/src/utils/fileUtils.d.ts +5 -0
- package/dist/src/utils/flattenConfig.d.ts +2 -0
- package/dist/src/utils/legacyAliases.d.ts +1 -0
- package/dist/src/utils/loadConfig.d.ts +16 -0
- package/dist/src/utils/packageImports.d.ts +4 -0
- package/dist/src/utils/sharedFoundation.d.ts +20 -0
- package/dist/src/utils/viteConfig.d.ts +9 -0
- package/package.json +48 -36
- package/src/checks/architecture.js +154 -0
- package/src/checks/checkDomain.js +124 -154
- package/src/checks/checkFoldersStructure.js +64 -52
- package/src/checks/checkHooks.js +12 -12
- package/src/checks/checkImports.js +26 -142
- package/src/checks/checkInternal.js +27 -24
- package/src/checks/checkLayerBoundaries.js +22 -197
- package/src/checks/checkLayerImports.js +14 -134
- package/src/checks/checkPresenterNames.js +156 -143
- package/src/checks/checkProject.js +27 -26
- package/src/checks/checkProviderGraph.js +182 -166
- package/src/checks/checkProviderNames.js +123 -112
- package/src/generate/layers/generateRepository.js +1 -109
- package/src/generate/layers/generateRepositoryMethod.js +1 -90
- package/src/generate/layers/generateUseCases.js +1 -44
- package/src/generate/module/generateDomain.js +81 -105
- package/src/generate/module/generateModule.js +34 -68
- package/src/generate/module/generateModulePreset.js +188 -248
- package/src/generate/module/generatePresenter.js +441 -369
- package/src/generate/module/generateRepository.js +102 -121
- package/src/generate/module/generateRepositoryMethod.js +117 -0
- package/src/generate/module/generateSchemaFromType.js +283 -280
- package/src/generate/module/generateStore.js +41 -0
- package/src/generate/module/generateTypesJson.js +196 -195
- package/src/generate/module/generateUseCases.js +47 -55
- package/src/generate/module/repositoryModuleShared.js +480 -459
- package/src/generate/routes/generateRoutes.js +4 -7
- package/src/generate/ui/generateComponent.js +44 -64
- package/src/generate/ui/generateDesignComponent.js +21 -41
- package/src/generate/ui/generateWidget.js +21 -41
- package/src/git/commit.js +67 -0
- package/src/git/push.js +19 -0
- package/src/git/runGit.js +11 -0
- package/src/init/initAliases.js +19 -135
- package/src/init/initEdges.js +79 -54
- package/src/init/initStructure.js +31 -145
- package/src/install/configureCommitTools.js +1 -0
- package/src/install/installFresh.js +39 -68
- package/src/migrate/migrate.js +914 -0
- package/src/migrate/presenters.js +265 -0
- package/src/plugins/addLint.js +52 -219
- package/src/scaffolds/createCoreSchema.js +65 -91
- package/src/scaffolds/createDatasource.js +67 -101
- package/src/scaffolds/createProgram.js +8 -8
- package/src/scaffolds/createResponse.js +29 -29
- package/src/scaffolds/createSchemaRule.js +19 -19
- package/src/scaffolds/createTranslationManager.js +84 -37
- package/src/scaffolds/index.js +0 -2
- package/src/scaffolds/registerDatasource.js +59 -0
- package/src/scaffolds/updateDefaultFactoryConfig.js +31 -0
- package/src/utils/architecture.js +145 -0
- package/src/utils/contextUtils.js +32 -65
- package/src/utils/fileUtils.js +69 -50
- package/src/utils/flattenConfig.js +38 -0
- package/src/utils/legacyAliases.js +75 -0
- package/src/utils/loadConfig.js +76 -40
- package/src/utils/packageImports.js +63 -0
- package/src/utils/sharedFoundation.js +36 -42
- package/src/utils/viteConfig.js +295 -0
- package/.prettierrc +0 -7
- package/bin/azure-net.ts +0 -308
- package/eslint.config.js +0 -34
- package/src/checks/checkDomain.ts +0 -223
- package/src/checks/checkFoldersStructure.ts +0 -85
- package/src/checks/checkHooks.ts +0 -22
- package/src/checks/checkImports.ts +0 -193
- package/src/checks/checkInternal.ts +0 -48
- package/src/checks/checkLayerBoundaries.ts +0 -255
- package/src/checks/checkLayerImports.ts +0 -185
- package/src/checks/checkPresenterNames.ts +0 -203
- package/src/checks/checkProject.ts +0 -45
- package/src/checks/checkProviderGraph.ts +0 -234
- package/src/checks/checkProviderNames.ts +0 -165
- package/src/generate/layers/generateRepository.ts +0 -123
- package/src/generate/layers/generateRepositoryMethod.ts +0 -102
- package/src/generate/layers/generateUseCases.ts +0 -50
- package/src/generate/layers/repositoryShared.js +0 -506
- package/src/generate/layers/repositoryShared.ts +0 -662
- package/src/generate/module/generateDomain.ts +0 -147
- package/src/generate/module/generateModule.ts +0 -84
- package/src/generate/module/generateModulePreset.ts +0 -338
- package/src/generate/module/generatePresenter.ts +0 -498
- package/src/generate/module/generateRepository.ts +0 -158
- package/src/generate/module/generateSchemaFromType.ts +0 -368
- package/src/generate/module/generateTypesJson.ts +0 -259
- package/src/generate/module/generateUseCases.ts +0 -67
- package/src/generate/module/repositoryModuleShared.ts +0 -669
- package/src/generate/routes/generateRoutes.ts +0 -275
- package/src/generate/ui/generateComponent.ts +0 -100
- package/src/generate/ui/generateDesignComponent.ts +0 -71
- package/src/generate/ui/generateWidget.ts +0 -71
- package/src/init/initAliases.ts +0 -172
- package/src/init/initEdges.ts +0 -74
- package/src/init/initStructure.ts +0 -172
- package/src/install/installFresh.ts +0 -93
- package/src/plugins/addLint.ts +0 -274
- package/src/scaffolds/createCoreSchema.ts +0 -130
- package/src/scaffolds/createDatasource.ts +0 -161
- package/src/scaffolds/createDatasourceProvider.js +0 -127
- package/src/scaffolds/createDatasourceProvider.ts +0 -161
- package/src/scaffolds/createPresenterFactory.js +0 -83
- package/src/scaffolds/createPresenterFactory.ts +0 -102
- package/src/scaffolds/createProgram.ts +0 -19
- package/src/scaffolds/createResponse.ts +0 -66
- package/src/scaffolds/createSchemaRule.ts +0 -44
- package/src/scaffolds/createTranslationManager.ts +0 -80
- package/src/utils/contextUtils.ts +0 -88
- package/src/utils/fileUtils.ts +0 -74
- package/src/utils/loadConfig.ts +0 -68
- package/src/utils/sharedFoundation.ts +0 -70
- package/tsconfig.json +0 -19
- /package/{src/checks/index.ts → dist/src/checks/index.d.ts} +0 -0
- /package/{src/plugins/index.ts → dist/src/plugins/index.d.ts} +0 -0
|
@@ -1,412 +1,407 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import prompts from 'prompts';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { toCamelCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
7
|
-
import { updateIndexTs } from '../../utils/fileUtils.js';
|
|
8
|
-
import { getFoundationConstructImportPath, getFoundationConstructPath, getSharedState } from '../../utils/sharedFoundation.js';
|
|
4
|
+
import { selectContext as selectDiskContext, toCamelCase, toPascalCase } from '../../utils/contextUtils.js';
|
|
5
|
+
import { updateIndexTs, writeIfNotExists } from '../../utils/fileUtils.js';
|
|
9
6
|
const HTTP_METHODS = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'];
|
|
10
7
|
const MUTATION_METHODS = new Set(['put', 'patch', 'delete']);
|
|
11
8
|
const RESERVED_IDENTIFIERS = new Set([
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
9
|
+
'break',
|
|
10
|
+
'case',
|
|
11
|
+
'catch',
|
|
12
|
+
'class',
|
|
13
|
+
'const',
|
|
14
|
+
'continue',
|
|
15
|
+
'debugger',
|
|
16
|
+
'default',
|
|
17
|
+
'delete',
|
|
18
|
+
'do',
|
|
19
|
+
'else',
|
|
20
|
+
'enum',
|
|
21
|
+
'export',
|
|
22
|
+
'extends',
|
|
23
|
+
'false',
|
|
24
|
+
'finally',
|
|
25
|
+
'for',
|
|
26
|
+
'function',
|
|
27
|
+
'if',
|
|
28
|
+
'import',
|
|
29
|
+
'in',
|
|
30
|
+
'instanceof',
|
|
31
|
+
'new',
|
|
32
|
+
'null',
|
|
33
|
+
'return',
|
|
34
|
+
'super',
|
|
35
|
+
'switch',
|
|
36
|
+
'this',
|
|
37
|
+
'throw',
|
|
38
|
+
'true',
|
|
39
|
+
'try',
|
|
40
|
+
'typeof',
|
|
41
|
+
'var',
|
|
42
|
+
'void',
|
|
43
|
+
'while',
|
|
44
|
+
'with',
|
|
45
|
+
'as',
|
|
46
|
+
'implements',
|
|
47
|
+
'interface',
|
|
48
|
+
'let',
|
|
49
|
+
'package',
|
|
50
|
+
'private',
|
|
51
|
+
'protected',
|
|
52
|
+
'public',
|
|
53
|
+
'static',
|
|
54
|
+
'yield'
|
|
58
55
|
]);
|
|
59
|
-
const
|
|
60
|
-
const raw = String(value ?? '').trim();
|
|
61
|
-
if (!raw.length) return fallback;
|
|
62
|
-
return raw.startsWith('$') ? raw : `$${raw}`;
|
|
63
|
-
};
|
|
64
|
-
const toTypeFileName = (typeName) => String(typeName ?? '').replace(/^I/, '') || 'Type';
|
|
56
|
+
const toTypeFileName = (typeName) => String(typeName ?? '').replace(/^I(?=[A-Z])/, '') || 'Type';
|
|
65
57
|
export const ensureRepositoryName = (rawName) => {
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
const normalized = toPascalCase(String(rawName ?? '').trim() || 'Repository');
|
|
59
|
+
return normalized.endsWith('Repository') ? normalized : `${normalized}Repository`;
|
|
68
60
|
};
|
|
69
61
|
export const getRepositoryMeta = (repositoryName) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
62
|
+
const className = ensureRepositoryName(repositoryName);
|
|
63
|
+
const baseName = className.replace(/Repository$/, '') || 'Repository';
|
|
64
|
+
const repositoryInterfaceName = `I${baseName}Repository`;
|
|
65
|
+
const useCasesClassName = `${baseName}UseCases`;
|
|
66
|
+
const repositoryDependencyNameRaw = toCamelCase(baseName || 'repository');
|
|
67
|
+
const repositoryDependencyName = RESERVED_IDENTIFIERS.has(repositoryDependencyNameRaw)
|
|
68
|
+
? `${repositoryDependencyNameRaw}Repository`
|
|
69
|
+
: repositoryDependencyNameRaw;
|
|
70
|
+
return {
|
|
71
|
+
className,
|
|
72
|
+
baseName,
|
|
73
|
+
repositoryInterfaceName,
|
|
74
|
+
useCasesClassName,
|
|
75
|
+
repositoryDependencyName
|
|
76
|
+
};
|
|
85
77
|
};
|
|
78
|
+
// Compatibility shape for presenter/schema consumers; contexts are discovered on disk.
|
|
86
79
|
export const getConfigState = async () => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const { context } = await prompts({
|
|
99
|
-
type: 'select',
|
|
100
|
-
name: 'context',
|
|
101
|
-
message,
|
|
102
|
-
choices: contexts.map((item) => ({ title: item.name, value: item.name })),
|
|
103
|
-
initial: 0
|
|
104
|
-
});
|
|
105
|
-
return context ? String(context) : null;
|
|
80
|
+
let contexts = [];
|
|
81
|
+
try {
|
|
82
|
+
const entries = await fs.readdir(path.join(process.cwd(), 'src/app/Contexts'), { withFileTypes: true });
|
|
83
|
+
contexts = entries
|
|
84
|
+
.filter((entry) => entry.isDirectory() && /^[A-Z][A-Za-z0-9]*$/.test(entry.name))
|
|
85
|
+
.map((entry) => ({ name: entry.name, alias: '#contexts/' + entry.name }));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* A new project may not contain any contexts yet. */
|
|
89
|
+
}
|
|
90
|
+
return { contexts, sharedAlias: '#platform', sharedContextName: 'Platform' };
|
|
106
91
|
};
|
|
107
|
-
export const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
92
|
+
export const selectContext = (message = 'Select context:') => selectDiskContext(message);
|
|
93
|
+
export const resolveContextAlias = (_contexts, contextName) => '#contexts/' + toPascalCase(contextName);
|
|
94
|
+
export const resolveRepositoriesPath = (contextName) => path.join(process.cwd(), 'src/app/Contexts', toPascalCase(contextName), 'Infrastructure/Repositories');
|
|
95
|
+
export const resolveDatasourcesPath = (...compatibility) => {
|
|
96
|
+
void compatibility;
|
|
97
|
+
return path.join(process.cwd(), 'src/app/Platform/Foundation/Datasource');
|
|
111
98
|
};
|
|
112
|
-
export const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
export const resolveSharedDatasourcesPath = async () => {
|
|
117
|
-
const { sharedContext } = await getSharedState();
|
|
118
|
-
return getFoundationConstructPath(sharedContext.name, 'datasource');
|
|
99
|
+
export const resolveSharedDatasourcesPath = async () => resolveDatasourcesPath();
|
|
100
|
+
export const resolveDomainRootPath = (contextName, ...compatibility) => {
|
|
101
|
+
void compatibility;
|
|
102
|
+
return path.join(process.cwd(), 'src/app/Contexts', toPascalCase(contextName), 'Domain');
|
|
119
103
|
};
|
|
120
|
-
export const
|
|
121
|
-
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'domain', domainName);
|
|
122
|
-
export const resolveUseCasesPath = (contextName) =>
|
|
123
|
-
path.join(process.cwd(), 'src', 'app', contextName, 'layers', 'application', 'use-cases');
|
|
104
|
+
export const resolveUseCasesPath = (contextName) => path.join(process.cwd(), 'src/app/Contexts', toPascalCase(contextName), 'Application/UseCases');
|
|
124
105
|
export const getAvailableTsNames = async (targetPath) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const contextAlias = resolveContextAlias(contexts, contextName);
|
|
135
|
-
const sharedDatasources = await getAvailableTsNames(await resolveSharedDatasourcesPath());
|
|
136
|
-
const contextDatasources = await getAvailableTsNames(resolveDatasourcesPath(contextName));
|
|
137
|
-
const choices = [];
|
|
138
|
-
for (const datasource of sharedDatasources) {
|
|
139
|
-
choices.push({
|
|
140
|
-
title: `${datasource} (shared foundation)`,
|
|
141
|
-
value: { name: datasource, importPath: getFoundationConstructImportPath(sharedAlias, 'datasource') }
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
for (const datasource of contextDatasources) {
|
|
145
|
-
choices.push({
|
|
146
|
-
title: `${datasource} (${contextName})`,
|
|
147
|
-
value: { name: datasource, importPath: `${contextAlias}/layers/infrastructure/http/datasources` }
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
if (!choices.length) {
|
|
151
|
-
console.error(`❌ No datasources found in shared foundation or context "${contextName}".`);
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
const { datasource } = await prompts({
|
|
155
|
-
type: 'select',
|
|
156
|
-
name: 'datasource',
|
|
157
|
-
message: 'Select datasource:',
|
|
158
|
-
choices,
|
|
159
|
-
initial: 0
|
|
160
|
-
});
|
|
161
|
-
return datasource ?? null;
|
|
106
|
+
try {
|
|
107
|
+
const files = await fs.readdir(targetPath);
|
|
108
|
+
return files
|
|
109
|
+
.filter((item) => item.endsWith('.ts') && !item.endsWith('.d.ts') && item !== 'index.ts')
|
|
110
|
+
.map((item) => item.replace(/\.ts$/, ''));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
162
115
|
};
|
|
163
|
-
export const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
});
|
|
179
|
-
return domain ? String(domain) : null;
|
|
180
|
-
} catch {
|
|
181
|
-
console.error(`❌ Domain path not found: ${domainPath}`);
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const extractInterfaceName = (content, fallback) => {
|
|
186
|
-
const match = content.match(/export\s+interface\s+([A-Za-z0-9_]+)/);
|
|
187
|
-
return match?.[1] ?? fallback;
|
|
116
|
+
export const selectDatasource = async (...compatibility) => {
|
|
117
|
+
void compatibility;
|
|
118
|
+
const names = await getAvailableTsNames(await resolveSharedDatasourcesPath());
|
|
119
|
+
if (!names.length) {
|
|
120
|
+
console.error('No datasources found in Platform/Foundation/Datasource. Create a Foundation datasource first.');
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const { datasource } = await prompts({
|
|
124
|
+
type: 'select',
|
|
125
|
+
name: 'datasource',
|
|
126
|
+
message: 'Select Foundation datasource:',
|
|
127
|
+
choices: names.map((name) => ({ title: name, value: { name, importPath: '#platform/Foundation/Datasource' } })),
|
|
128
|
+
initial: 0
|
|
129
|
+
});
|
|
130
|
+
return datasource ?? null;
|
|
188
131
|
};
|
|
132
|
+
// Retained for callers using the old metadata shape; there is no domain-folder selector.
|
|
133
|
+
export const selectDomain = async (contextName) => toPascalCase(contextName);
|
|
189
134
|
export const getDomainTypes = async (contextName, domainName) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
135
|
+
const root = resolveDomainRootPath(contextName, domainName);
|
|
136
|
+
const result = [];
|
|
137
|
+
const readLayer = async (layer, relative = '') => {
|
|
138
|
+
const directory = path.join(root, layer, relative);
|
|
139
|
+
let entries;
|
|
140
|
+
try {
|
|
141
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error.code === 'ENOENT')
|
|
145
|
+
return;
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
await readLayer(layer, path.join(relative, entry.name));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!entry.name.endsWith('.ts') || entry.name.endsWith('.d.ts') || entry.name === 'index.ts')
|
|
154
|
+
continue;
|
|
155
|
+
const content = await fs.readFile(path.join(directory, entry.name), 'utf8');
|
|
156
|
+
for (const match of content.matchAll(/export\s+(?:declare\s+)?(?:interface|type|(?:const\s+)?enum|class)\s+([A-Za-z_$][\w$]*)/g)) {
|
|
157
|
+
result.push({ name: match[1], layer, fileName: path.join(relative, entry.name.slice(0, -3)).split(path.sep).join('/') });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
for (const layer of ['Ports', 'Entities', 'Enums'])
|
|
162
|
+
await readLayer(layer);
|
|
163
|
+
return result;
|
|
164
|
+
};
|
|
165
|
+
// Type expressions are not import specifiers: import their referenced domain symbols only.
|
|
166
|
+
const referencedTypes = (expression, types) => {
|
|
167
|
+
const identifiers = new Set(expression.replace(/(['"])(?:\\.|(?!\1).)*?\1/g, '').match(/[A-Za-z_$][\w$]*/g) ?? []);
|
|
168
|
+
return types.filter((type) => identifiers.has(type.name));
|
|
211
169
|
};
|
|
212
|
-
const isPrimitiveType = (value) =>
|
|
213
|
-
['string', 'number', 'boolean', 'unknown', 'void', 'null', 'undefined', 'bigint', 'symbol', 'never'].includes(String(value ?? '').trim());
|
|
170
|
+
const isPrimitiveType = (value) => ['string', 'number', 'boolean', 'unknown', 'void', 'null', 'undefined', 'bigint', 'symbol', 'never'].includes(String(value ?? '').trim());
|
|
214
171
|
const buildResponseSelection = async (domainTypes) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
172
|
+
const { isPrimitive } = await prompts({
|
|
173
|
+
type: 'confirm',
|
|
174
|
+
name: 'isPrimitive',
|
|
175
|
+
message: 'Is response type primitive?',
|
|
176
|
+
initial: false
|
|
177
|
+
});
|
|
178
|
+
if (isPrimitive) {
|
|
179
|
+
const { primitiveType } = await prompts({
|
|
180
|
+
type: 'text',
|
|
181
|
+
name: 'primitiveType',
|
|
182
|
+
message: 'Primitive response type:',
|
|
183
|
+
validate: (value) => (isPrimitiveType(value) ? true : 'Enter a primitive type such as string, unknown or never')
|
|
184
|
+
});
|
|
185
|
+
return { kind: 'primitive', type: String(primitiveType).trim(), layer: null };
|
|
186
|
+
}
|
|
187
|
+
if (!domainTypes.length) {
|
|
188
|
+
console.error('❌ Domain has no Entities/Ports/Enums types to choose from.');
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const { selectedType } = await prompts({
|
|
192
|
+
type: 'select',
|
|
193
|
+
name: 'selectedType',
|
|
194
|
+
message: 'Select response type:',
|
|
195
|
+
choices: domainTypes.map((item) => ({
|
|
196
|
+
title: `${item.name} (${item.layer})`,
|
|
197
|
+
value: item
|
|
198
|
+
})),
|
|
199
|
+
initial: 0
|
|
200
|
+
});
|
|
201
|
+
if (!selectedType)
|
|
202
|
+
return null;
|
|
203
|
+
return { kind: 'domain', type: selectedType.name, layer: selectedType.layer };
|
|
246
204
|
};
|
|
247
205
|
const buildDomainRequestTypeSelection = async (question, domainTypes) => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
206
|
+
if (!domainTypes.length) {
|
|
207
|
+
console.error('❌ Domain has no model/ports types to choose from.');
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const { enabled } = await prompts({
|
|
211
|
+
type: 'confirm',
|
|
212
|
+
name: 'enabled',
|
|
213
|
+
message: question,
|
|
214
|
+
initial: false
|
|
215
|
+
});
|
|
216
|
+
if (!enabled)
|
|
217
|
+
return null;
|
|
218
|
+
const { selectedType } = await prompts({
|
|
219
|
+
type: 'select',
|
|
220
|
+
name: 'selectedType',
|
|
221
|
+
message: 'Select type:',
|
|
222
|
+
choices: domainTypes.map((item) => ({
|
|
223
|
+
title: `${item.name} (${item.layer})`,
|
|
224
|
+
value: item
|
|
225
|
+
})),
|
|
226
|
+
initial: 0
|
|
227
|
+
});
|
|
228
|
+
return selectedType ?? null;
|
|
270
229
|
};
|
|
271
230
|
const buildPathParamTypeSelection = async () => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
231
|
+
const { enabled } = await prompts({
|
|
232
|
+
type: 'confirm',
|
|
233
|
+
name: 'enabled',
|
|
234
|
+
message: 'Has path param?',
|
|
235
|
+
initial: false
|
|
236
|
+
});
|
|
237
|
+
if (!enabled)
|
|
238
|
+
return null;
|
|
239
|
+
const { type } = await prompts({
|
|
240
|
+
type: 'select',
|
|
241
|
+
name: 'type',
|
|
242
|
+
message: 'Path param type:',
|
|
243
|
+
choices: [
|
|
244
|
+
{ title: 'string', value: 'string' },
|
|
245
|
+
{ title: 'number', value: 'number' }
|
|
246
|
+
],
|
|
247
|
+
initial: 0
|
|
248
|
+
});
|
|
249
|
+
return type ?? 'string';
|
|
290
250
|
};
|
|
291
251
|
export const promptMethodDefinition = async (domainTypes) => {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
252
|
+
const { methodNameRaw } = await prompts({
|
|
253
|
+
type: 'text',
|
|
254
|
+
name: 'methodNameRaw',
|
|
255
|
+
message: 'Method name:',
|
|
256
|
+
validate: (value) => (String(value ?? '').trim().length > 0 ? true : 'Method name is required')
|
|
257
|
+
});
|
|
258
|
+
const { httpMethod } = await prompts({
|
|
259
|
+
type: 'select',
|
|
260
|
+
name: 'httpMethod',
|
|
261
|
+
message: 'HTTP method:',
|
|
262
|
+
choices: HTTP_METHODS.map((item) => ({ title: item, value: item })),
|
|
263
|
+
initial: 0
|
|
264
|
+
});
|
|
265
|
+
const { routeRaw } = await prompts({
|
|
266
|
+
type: 'text',
|
|
267
|
+
name: 'routeRaw',
|
|
268
|
+
message: 'Request path (e.g. /public-route):',
|
|
269
|
+
initial: '/public-route',
|
|
270
|
+
validate: (value) => {
|
|
271
|
+
const raw = String(value ?? '').trim();
|
|
272
|
+
if (!raw.length)
|
|
273
|
+
return 'Path is required';
|
|
274
|
+
return raw.startsWith('/') ? true : 'Path must start with "/"';
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const response = await buildResponseSelection(domainTypes);
|
|
278
|
+
if (!response)
|
|
279
|
+
return null;
|
|
280
|
+
const normalizedMethod = String(httpMethod ?? 'get').toLowerCase();
|
|
281
|
+
let queryType = null;
|
|
282
|
+
let bodyType = null;
|
|
283
|
+
let pathParamType = null;
|
|
284
|
+
if (normalizedMethod === 'get') {
|
|
285
|
+
queryType = await buildDomainRequestTypeSelection('Has query params?', domainTypes);
|
|
286
|
+
}
|
|
287
|
+
else if (normalizedMethod === 'post') {
|
|
288
|
+
bodyType = await buildDomainRequestTypeSelection('Has request body?', domainTypes);
|
|
289
|
+
}
|
|
290
|
+
else if (MUTATION_METHODS.has(normalizedMethod)) {
|
|
291
|
+
pathParamType = await buildPathParamTypeSelection();
|
|
292
|
+
bodyType = await buildDomainRequestTypeSelection('Has request body?', domainTypes);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
name: toCamelCase(String(methodNameRaw ?? '').trim() || 'method'),
|
|
296
|
+
httpMethod: normalizedMethod,
|
|
297
|
+
route: String(routeRaw ?? '/public-route').trim() || '/public-route',
|
|
298
|
+
response,
|
|
299
|
+
queryType,
|
|
300
|
+
bodyType,
|
|
301
|
+
pathParamType
|
|
302
|
+
};
|
|
339
303
|
};
|
|
340
304
|
const getResponseTypeName = (method) => method.response.type;
|
|
341
305
|
const getMethodParams = (method) => {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
306
|
+
const params = [];
|
|
307
|
+
if (method.pathParamType)
|
|
308
|
+
params.push(`pathParam: ${method.pathParamType}`);
|
|
309
|
+
if (method.queryType)
|
|
310
|
+
params.push(`request: ${method.queryType.name}`);
|
|
311
|
+
if (method.bodyType)
|
|
312
|
+
params.push(`request: ${method.bodyType.name}`);
|
|
313
|
+
return params.join(', ');
|
|
347
314
|
};
|
|
348
315
|
const getRequestMapping = (method) => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
316
|
+
const escapedRoute = method.route
|
|
317
|
+
.replace(/\\/g, '\\\\')
|
|
318
|
+
.replace(/`/g, '\\`')
|
|
319
|
+
.replace(/\$\{/g, '\\${')
|
|
320
|
+
.replace(/\r/g, '\\r')
|
|
321
|
+
.replace(/\n/g, '\\n');
|
|
322
|
+
const routeWithPath = method.pathParamType ? `${escapedRoute}/\${pathParam}` : escapedRoute;
|
|
323
|
+
if (method.httpMethod === 'get') {
|
|
324
|
+
if (method.queryType) {
|
|
325
|
+
return {
|
|
326
|
+
callbackParams: '{ http, query }',
|
|
327
|
+
call: `http.get(\`${routeWithPath}\`, { searchParams: query.toSearchParams(request) })`
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
callbackParams: '{ http }',
|
|
332
|
+
call: `http.get(\`${routeWithPath}\`)`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (method.httpMethod === 'post') {
|
|
336
|
+
if (method.bodyType) {
|
|
337
|
+
return {
|
|
338
|
+
callbackParams: '{ http }',
|
|
339
|
+
call: `http.post(\`${routeWithPath}\`, { json: request })`
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
callbackParams: '{ http }',
|
|
344
|
+
call: `http.post(\`${routeWithPath}\`)`
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (method.httpMethod === 'put' || method.httpMethod === 'patch' || method.httpMethod === 'delete') {
|
|
348
|
+
const callMethod = method.httpMethod;
|
|
349
|
+
if (method.bodyType) {
|
|
350
|
+
return {
|
|
351
|
+
callbackParams: '{ http }',
|
|
352
|
+
call: `http.${callMethod}(\`${routeWithPath}\`, { json: request })`
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
callbackParams: '{ http }',
|
|
357
|
+
call: `http.${callMethod}(\`${routeWithPath}\`)`
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
callbackParams: '{ http }',
|
|
362
|
+
call: `http.${method.httpMethod}(\`${routeWithPath}\`)`
|
|
363
|
+
};
|
|
391
364
|
};
|
|
392
365
|
export const buildRepositoryMethodCode = (method) => {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
366
|
+
const params = getMethodParams(method);
|
|
367
|
+
const responseType = getResponseTypeName(method);
|
|
368
|
+
const mapping = getRequestMapping(method);
|
|
369
|
+
return `\tpublic async ${method.name}(${params}) {\n\t\treturn this.datasource\n\t\t\t.createRequest<${responseType}>((${mapping.callbackParams}) => ${mapping.call})\n\t\t\t.then((res: { getData(): ${responseType} } | { data: ${responseType} }) => 'getData' in res ? res.getData() : res.data);\n\t}`;
|
|
397
370
|
};
|
|
398
|
-
export const buildRepositoryContent = ({ meta, contextAlias,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
371
|
+
export const buildRepositoryContent = ({ meta, contextAlias, datasource, domainTypes = [], methods }) => {
|
|
372
|
+
const importLines = [
|
|
373
|
+
`import type { ${meta.repositoryInterfaceName} } from '${contextAlias}/Domain/Ports/${toTypeFileName(meta.repositoryInterfaceName)}';`
|
|
374
|
+
];
|
|
375
|
+
const imported = new Set();
|
|
376
|
+
for (const method of methods) {
|
|
377
|
+
for (const expression of [method.response.type, method.queryType?.name, method.bodyType?.name]) {
|
|
378
|
+
if (!expression)
|
|
379
|
+
continue;
|
|
380
|
+
for (const type of referencedTypes(expression, domainTypes)) {
|
|
381
|
+
if (imported.has(type.name))
|
|
382
|
+
continue;
|
|
383
|
+
imported.add(type.name);
|
|
384
|
+
importLines.push(`import type { ${type.name} } from '${contextAlias}/Domain/${type.layer}/${type.fileName ?? toTypeFileName(type.name)}';`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Preserve the helper contract for callers that do not supply discovery metadata.
|
|
389
|
+
if (!domainTypes.length) {
|
|
390
|
+
const names = new Set();
|
|
391
|
+
for (const method of methods) {
|
|
392
|
+
if (method.response.kind === 'domain')
|
|
393
|
+
names.add(method.response.type);
|
|
394
|
+
if (method.queryType)
|
|
395
|
+
names.add(method.queryType.name);
|
|
396
|
+
if (method.bodyType)
|
|
397
|
+
names.add(method.bodyType.name);
|
|
398
|
+
}
|
|
399
|
+
if (names.size)
|
|
400
|
+
importLines.push(`import type { ${[...names].join(', ')} } from '${contextAlias}/Domain';`);
|
|
401
|
+
}
|
|
402
|
+
const methodsCode = methods.map((method) => buildRepositoryMethodCode(method)).join('\n\n');
|
|
403
|
+
const typeImportLine = importLines.join('\n') + '\n';
|
|
404
|
+
return `import { ${datasource.name} } from '${datasource.importPath}';
|
|
410
405
|
${typeImportLine}
|
|
411
406
|
export class ${meta.className} implements ${meta.repositoryInterfaceName} {
|
|
412
407
|
\tconstructor(private readonly datasource: ${datasource.name}) {}
|
|
@@ -416,77 +411,70 @@ ${methodsCode ? `${methodsCode}\n` : ''}
|
|
|
416
411
|
`;
|
|
417
412
|
};
|
|
418
413
|
export const writeRepositoryInterface = async ({ contextName, domainName, meta, methods }) => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
await updateIndexTs(portsPath);
|
|
453
|
-
};
|
|
454
|
-
const parseRepositoryFileForDomain = (content, contextAlias) => {
|
|
455
|
-
const escapedAlias = contextAlias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
456
|
-
const regex = new RegExp(`from\\s+['"]${escapedAlias}/layers/domain/([^'"]+)['"]`);
|
|
457
|
-
const match = content.match(regex);
|
|
458
|
-
return match?.[1] ?? null;
|
|
414
|
+
const domainPath = resolveDomainRootPath(contextName, domainName);
|
|
415
|
+
const portsPath = path.join(domainPath, 'Ports');
|
|
416
|
+
await fs.mkdir(portsPath, { recursive: true });
|
|
417
|
+
const types = await getDomainTypes(contextName, domainName);
|
|
418
|
+
const imports = [];
|
|
419
|
+
const imported = new Set();
|
|
420
|
+
for (const method of methods) {
|
|
421
|
+
for (const expression of [method.response.type, method.queryType?.name, method.bodyType?.name]) {
|
|
422
|
+
if (!expression)
|
|
423
|
+
continue;
|
|
424
|
+
for (const type of referencedTypes(expression, types)) {
|
|
425
|
+
if (imported.has(type.name) || type.name === meta.repositoryInterfaceName)
|
|
426
|
+
continue;
|
|
427
|
+
imported.add(type.name);
|
|
428
|
+
const source = type.layer === 'Ports' ? './' : '../' + type.layer + '/';
|
|
429
|
+
imports.push(`import type { ${type.name} } from '${source}${type.fileName}';`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const signatures = methods
|
|
434
|
+
.map((method) => {
|
|
435
|
+
const responseType = getResponseTypeName(method);
|
|
436
|
+
const params = getMethodParams(method);
|
|
437
|
+
return `\t${method.name}(${params}): Promise<${responseType}>;`;
|
|
438
|
+
})
|
|
439
|
+
.join('\n');
|
|
440
|
+
const content = `${imports.join('\n')}${imports.length ? '\n\n' : ''}export interface ${meta.repositoryInterfaceName} {\n${signatures ? `${signatures}\n` : ''}}\n`;
|
|
441
|
+
const created = await writeIfNotExists(path.join(portsPath, `${toTypeFileName(meta.repositoryInterfaceName)}.ts`), content);
|
|
442
|
+
if (!created)
|
|
443
|
+
return false;
|
|
444
|
+
await updateIndexTs(portsPath);
|
|
445
|
+
await ensureDomainScaffold(contextName);
|
|
446
|
+
return true;
|
|
459
447
|
};
|
|
460
448
|
const parseRepositoryMethodsFromInterface = (content) => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
449
|
+
const methods = [];
|
|
450
|
+
const regex = /^\s*(\w+)\s*(?:<[^\n]+>)?\s*\(/gm;
|
|
451
|
+
let match;
|
|
452
|
+
while ((match = regex.exec(content)) !== null) {
|
|
453
|
+
const methodName = match[1];
|
|
454
|
+
if (methodName)
|
|
455
|
+
methods.push(methodName);
|
|
456
|
+
}
|
|
457
|
+
return methods;
|
|
469
458
|
};
|
|
470
459
|
export const createUseCasesForRepository = async ({ contextName, contextAlias, domainName, repositoryName }) => {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
import type { ${meta.repositoryInterfaceName} } from '${contextAlias}/layers/domain/${domainName}';
|
|
460
|
+
const meta = getRepositoryMeta(repositoryName);
|
|
461
|
+
const type = (await getDomainTypes(contextName, domainName)).find((type) => type.name === meta.repositoryInterfaceName && type.layer === 'Ports');
|
|
462
|
+
const interfaceFileName = type?.fileName ?? toTypeFileName(meta.repositoryInterfaceName);
|
|
463
|
+
const interfacePath = path.join(resolveDomainRootPath(contextName), 'Ports', interfaceFileName + '.ts');
|
|
464
|
+
let interfaceContent = '';
|
|
465
|
+
try {
|
|
466
|
+
interfaceContent = await fs.readFile(interfacePath, 'utf-8');
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
console.error(`❌ Repository interface not found: ${interfacePath}`);
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
const methods = parseRepositoryMethodsFromInterface(interfaceContent);
|
|
473
|
+
const useCasesPath = resolveUseCasesPath(contextName);
|
|
474
|
+
await fs.mkdir(useCasesPath, { recursive: true });
|
|
475
|
+
const declares = methods.map((methodName) => `\tdeclare ${methodName}: ${meta.repositoryInterfaceName}['${methodName}'];`).join('\n');
|
|
476
|
+
const content = `import { ClassMirror } from '@azure-net/kit';
|
|
477
|
+
import type { ${meta.repositoryInterfaceName} } from '${contextAlias}/Domain/Ports/${interfaceFileName}';
|
|
490
478
|
|
|
491
479
|
export class ${meta.useCasesClassName} extends ClassMirror {
|
|
492
480
|
\tconstructor(private ${meta.repositoryDependencyName}: ${meta.repositoryInterfaceName}) {
|
|
@@ -496,26 +484,59 @@ export class ${meta.useCasesClassName} extends ClassMirror {
|
|
|
496
484
|
${declares ? `${declares}\n` : ''}
|
|
497
485
|
}
|
|
498
486
|
`;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
487
|
+
const created = await writeIfNotExists(path.join(useCasesPath, `${meta.useCasesClassName}.ts`), content);
|
|
488
|
+
if (!created)
|
|
489
|
+
return false;
|
|
490
|
+
await updateIndexTs(useCasesPath);
|
|
491
|
+
return true;
|
|
502
492
|
};
|
|
503
493
|
export const resolveDomainForRepository = async ({ contextName, contextAlias, repositoryName }) => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
494
|
+
const repositoryPath = path.join(resolveRepositoriesPath(contextName), `${ensureRepositoryName(repositoryName)}.ts`);
|
|
495
|
+
try {
|
|
496
|
+
const content = await fs.readFile(repositoryPath, 'utf-8');
|
|
497
|
+
return content.includes(`${contextAlias}/Domain`) ? toPascalCase(contextName) : null;
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
511
502
|
};
|
|
512
503
|
export const promptMethodsBatch = async (domainTypes, count) => {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
504
|
+
const methods = [];
|
|
505
|
+
for (let index = 0; index < count; index += 1) {
|
|
506
|
+
console.log(`\n🧩 Method ${index + 1} of ${count}`);
|
|
507
|
+
const method = await promptMethodDefinition(domainTypes);
|
|
508
|
+
if (!method)
|
|
509
|
+
return null;
|
|
510
|
+
if (method.name === 'constructor' || methods.some((item) => item.name === method.name))
|
|
511
|
+
throw new Error('Duplicate or reserved method name: ' + method.name);
|
|
512
|
+
methods.push(method);
|
|
513
|
+
}
|
|
514
|
+
return methods;
|
|
515
|
+
};
|
|
516
|
+
export const ensureDomainScaffold = async (contextName) => {
|
|
517
|
+
const root = resolveDomainRootPath(contextName);
|
|
518
|
+
for (const layer of ['Entities', 'Ports', 'Enums']) {
|
|
519
|
+
const directory = path.join(root, layer);
|
|
520
|
+
await fs.mkdir(directory, { recursive: true });
|
|
521
|
+
await updateIndexTs(directory);
|
|
522
|
+
}
|
|
523
|
+
await writeIfNotExists(path.join(root, 'index.ts'), "export * from './Entities';\nexport * from './Ports';\nexport * from './Enums';\n");
|
|
524
|
+
};
|
|
525
|
+
export const printApplicationProviderWiring = (contextName, repositoryName, datasourceName) => {
|
|
526
|
+
const meta = getRepositoryMeta(repositoryName);
|
|
527
|
+
const alias = resolveContextAlias([], contextName);
|
|
528
|
+
console.log('\nManually wire #platform/Foundation/DI/ApplicationProvider.ts before generating the presenter.');
|
|
529
|
+
console.log(`import { ${datasourceName} } from '#platform/Foundation/Datasource';
|
|
530
|
+
import { ${meta.className} } from '${alias}/Infrastructure/Repositories';
|
|
531
|
+
import { ${meta.useCasesClassName} } from '${alias}/Application/UseCases';
|
|
532
|
+
|
|
533
|
+
// Merge into the existing ApplicationProvider configuration.
|
|
534
|
+
register: () => {
|
|
535
|
+
const repository = () => new ${meta.className}(new ${datasourceName}({ /* http, query */ }));
|
|
536
|
+
return {
|
|
537
|
+
${meta.useCasesClassName}: () => new ${meta.useCasesClassName}(repository()),
|
|
538
|
+
// Optional: expose the repository as a lazy factory too.
|
|
539
|
+
// ${meta.className}: repository,
|
|
540
|
+
};
|
|
541
|
+
}`);
|
|
521
542
|
};
|