@azure-net/cli 1.4.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/azure-net.js +83 -32
- package/package.json +1 -1
- package/src/commands/generate/generateCrud.js +323 -0
- package/src/commands/generate/generateDatasource.js +36 -0
- package/src/commands/generate/generateModule.js +23 -0
- package/src/commands/generate/generatePresenter.js +113 -0
- package/src/commands/generate/generateRepository.js +97 -0
- package/src/commands/generate/generateResponse.js +42 -0
- package/src/commands/generate/generateSchema.js +70 -0
- package/src/commands/generate/generateService.js +93 -0
- package/src/commands/generate/index.js +8 -0
- package/src/commands/init/index.js +6 -0
- package/src/commands/init/initAliases.js +50 -0
- package/src/commands/init/initEdges.js +42 -0
- package/src/commands/init/initMiddleware.js +58 -0
- package/src/commands/init/initSchema.js +19 -0
- package/src/commands/init/initStructure.js +68 -0
- package/src/commands/init/initTranslations.js +97 -0
- package/src/templates/domain/request/ClassRequest.ejs +8 -0
- package/src/utils/contextUtils.js +53 -0
- package/src/utils/fileUtils.js +60 -0
- package/src/utils/loadConfig.js +2 -2
- package/src/utils/providerUtils.js +78 -0
- package/.idea/azure-net-ecosystem-cli.iml +0 -9
- package/.idea/modules.xml +0 -8
- package/src/commands/generateCrud.js +0 -135
- package/src/commands/generateDatasource.js +0 -44
- package/src/commands/generateRepo.js +0 -69
- package/src/commands/generateService.js +0 -77
- package/src/commands/generateStructure.js +0 -65
- package/src/commands/initAliases.js +0 -101
- package/src/commands/initLint.js +0 -138
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { loadUserConfig } from './loadConfig.js';
|
|
5
|
+
|
|
6
|
+
const CONTEXTS_PATH = path.join(process.cwd(), 'src/app/contexts');
|
|
7
|
+
const CORE_PATH = path.join(process.cwd(), 'src/app/core');
|
|
8
|
+
|
|
9
|
+
export async function selectContext(message = 'Select context:') {
|
|
10
|
+
const config = await loadUserConfig();
|
|
11
|
+
const contexts = config.contexts || ['app'];
|
|
12
|
+
|
|
13
|
+
const { context } = await prompts({
|
|
14
|
+
type: 'select',
|
|
15
|
+
name: 'context',
|
|
16
|
+
message,
|
|
17
|
+
choices: [
|
|
18
|
+
...contexts.map(c => ({ title: c, value: c })),
|
|
19
|
+
{ title: 'core (shared)', value: 'core' }
|
|
20
|
+
]
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return context;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getContextPath(context) {
|
|
27
|
+
return context === 'core'
|
|
28
|
+
? CORE_PATH
|
|
29
|
+
: path.join(CONTEXTS_PATH, context);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getAvailableFiles(dir, pattern = '.ts') {
|
|
33
|
+
try {
|
|
34
|
+
const files = await fs.readdir(dir);
|
|
35
|
+
return files
|
|
36
|
+
.filter(f => f.endsWith(pattern) && f !== 'index.ts')
|
|
37
|
+
.map(f => f.replace(pattern, ''));
|
|
38
|
+
} catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function toPascalCase(str) {
|
|
44
|
+
return str
|
|
45
|
+
.split(/[-_\s]+/)
|
|
46
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
47
|
+
.join('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function toCamelCase(str) {
|
|
51
|
+
const pascal = toPascalCase(str);
|
|
52
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
53
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export async function writeIfNotExists(filepath, content) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(filepath);
|
|
7
|
+
console.log(`⚠️ Skip existing: ${filepath}`);
|
|
8
|
+
return false;
|
|
9
|
+
} catch {
|
|
10
|
+
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
|
11
|
+
await fs.writeFile(filepath, content, 'utf-8');
|
|
12
|
+
console.log(`✅ Created: ${filepath}`);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function updateIndexTs(dir, filePattern = '.ts', ignore = ['index.ts']) {
|
|
18
|
+
try {
|
|
19
|
+
const files = (await fs.readdir(dir)).filter(f =>
|
|
20
|
+
f.endsWith(filePattern) && !ignore.includes(f)
|
|
21
|
+
);
|
|
22
|
+
const content = files
|
|
23
|
+
.map(f => `export * from './${f.replace('.ts', '')}.js';`)
|
|
24
|
+
.join('\n') + '\n';
|
|
25
|
+
await fs.writeFile(path.join(dir, 'index.ts'), content, 'utf-8');
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// Directory doesn't exist yet
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function injectIntoFile(filepath, searchPattern, newContent, position = 'after') {
|
|
32
|
+
try {
|
|
33
|
+
let content = await fs.readFile(filepath, 'utf-8');
|
|
34
|
+
const index = content.indexOf(searchPattern);
|
|
35
|
+
|
|
36
|
+
if (index === -1) {
|
|
37
|
+
console.warn(`Pattern not found in ${filepath}`);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (content.includes(newContent.trim())) {
|
|
42
|
+
console.log(`Content already exists in ${filepath}`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (position === 'after') {
|
|
47
|
+
const insertIndex = index + searchPattern.length;
|
|
48
|
+
content = content.slice(0, insertIndex) + '\n' + newContent + content.slice(insertIndex);
|
|
49
|
+
} else {
|
|
50
|
+
content = content.slice(0, index) + newContent + '\n' + content.slice(index);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await fs.writeFile(filepath, content, 'utf-8');
|
|
54
|
+
console.log(`✅ Updated: ${filepath}`);
|
|
55
|
+
return true;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Failed to update ${filepath}:`, error.message);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/utils/loadConfig.js
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { toPascalCase, toCamelCase } from './contextUtils.js';
|
|
4
|
+
import { writeIfNotExists, updateIndexTs } from './fileUtils.js';
|
|
5
|
+
|
|
6
|
+
export async function ensureProvider(context, layer, dependencies = {}) {
|
|
7
|
+
const providerName = `${toPascalCase(context)}${layer}Provider`;
|
|
8
|
+
const contextPath = path.join(process.cwd(), 'src/app/contexts', context);
|
|
9
|
+
const providerPath = path.join(contextPath, layer, 'Providers', `${layer}Provider.ts`);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await fs.access(providerPath);
|
|
13
|
+
return { exists: true, name: providerName, path: providerPath };
|
|
14
|
+
} catch {
|
|
15
|
+
// Provider doesn't exist, create it
|
|
16
|
+
const content = generateProviderContent(context, layer, dependencies);
|
|
17
|
+
await writeIfNotExists(providerPath, content);
|
|
18
|
+
await updateIndexTs(path.dirname(providerPath));
|
|
19
|
+
await updateIndexTs(path.join(contextPath, layer));
|
|
20
|
+
return { exists: false, name: providerName, path: providerPath };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function generateProviderContent(context, layer, dependencies) {
|
|
25
|
+
const providerName = `${toPascalCase(context)}${layer}BoundaryProvider`;
|
|
26
|
+
const varName = `${layer}Provider`;
|
|
27
|
+
|
|
28
|
+
let imports = [`import { createBoundaryProvider } from '@azure-net/kit';`];
|
|
29
|
+
let deps = [];
|
|
30
|
+
let factories = [];
|
|
31
|
+
|
|
32
|
+
if (dependencies.datasource && layer === 'Infrastructure') {
|
|
33
|
+
imports.push(`import { ${dependencies.datasource} } from '../Datasources/index.js';`);
|
|
34
|
+
factories.push(`\t${dependencies.datasource}: () => new ${dependencies.datasource}()`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (dependencies.infrastructure && layer === 'Application') {
|
|
38
|
+
imports.push(`import { InfrastructureProvider } from '../../Infrastructure/index.js';`);
|
|
39
|
+
deps.push('InfrastructureProvider');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dependsOnStr = deps.length > 0
|
|
43
|
+
? `,\n\t{ dependsOn: { ${deps.join(', ')} } }`
|
|
44
|
+
: '';
|
|
45
|
+
|
|
46
|
+
return `${imports.join('\n')}
|
|
47
|
+
|
|
48
|
+
export const ${varName} = createBoundaryProvider(
|
|
49
|
+
\t'${providerName}',
|
|
50
|
+
\t(${deps.length > 0 ? `{ ${deps.join(', ')} }` : ''}) => ({
|
|
51
|
+
${factories.join(',\n')}
|
|
52
|
+
\t})${dependsOnStr}
|
|
53
|
+
);`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function addToProvider(providerPath, serviceName, importPath) {
|
|
57
|
+
let content = await fs.readFile(providerPath, 'utf-8');
|
|
58
|
+
|
|
59
|
+
// Add import if not exists
|
|
60
|
+
const importStatement = `import { ${serviceName} } from '${importPath}';`;
|
|
61
|
+
if (!content.includes(importStatement)) {
|
|
62
|
+
const lastImportIndex = content.lastIndexOf('import ');
|
|
63
|
+
const endOfImportLine = content.indexOf('\n', lastImportIndex);
|
|
64
|
+
content = content.slice(0, endOfImportLine + 1) + importStatement + '\n' + content.slice(endOfImportLine + 1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add factory method
|
|
68
|
+
const factoryLine = `\t\t${serviceName}: () => new ${serviceName}()`;
|
|
69
|
+
const returnIndex = content.indexOf('=> ({');
|
|
70
|
+
const endOfReturn = content.indexOf('\t})', returnIndex);
|
|
71
|
+
|
|
72
|
+
if (!content.includes(factoryLine)) {
|
|
73
|
+
const beforeEnd = content.lastIndexOf('\n', endOfReturn);
|
|
74
|
+
content = content.slice(0, beforeEnd) + ',\n' + factoryLine + content.slice(beforeEnd);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await fs.writeFile(providerPath, content, 'utf-8');
|
|
78
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="WEB_MODULE" version="4">
|
|
3
|
-
<component name="Go" enabled="true" />
|
|
4
|
-
<component name="NewModuleRootManager">
|
|
5
|
-
<content url="file://$MODULE_DIR$" />
|
|
6
|
-
<orderEntry type="inheritedJdk" />
|
|
7
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
8
|
-
</component>
|
|
9
|
-
</module>
|
package/.idea/modules.xml
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="ProjectModuleManager">
|
|
4
|
-
<modules>
|
|
5
|
-
<module fileurl="file://$PROJECT_DIR$/.idea/azure-net-ecosystem-cli.iml" filepath="$PROJECT_DIR$/.idea/azure-net-ecosystem-cli.iml" />
|
|
6
|
-
</modules>
|
|
7
|
-
</component>
|
|
8
|
-
</project>
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import prompts from 'prompts';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import ejs from 'ejs';
|
|
5
|
-
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
8
|
-
const CORE_DATASOURCES_PATH = path.join(process.cwd(), 'src/app/core/datasources');
|
|
9
|
-
|
|
10
|
-
async function writeIfNotExists(filepath, content) {
|
|
11
|
-
try {
|
|
12
|
-
await fs.access(filepath);
|
|
13
|
-
console.log(`⚠️ Skip existing: ${filepath}`);
|
|
14
|
-
} catch {
|
|
15
|
-
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
|
16
|
-
await fs.writeFile(filepath, content, 'utf-8');
|
|
17
|
-
console.log(`✅ Created: ${filepath}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function updateIndexTs(dir, filePattern = '.ts', ignore = ['index.ts']) {
|
|
22
|
-
const files = (await fs.readdir(dir)).filter(f => f.endsWith(filePattern) && !ignore.includes(f));
|
|
23
|
-
const content = files.map(f => `export * from './${f.replace('.ts', '')}';`).join('\n') + '\n';
|
|
24
|
-
await fs.writeFile(path.join(dir, 'index.ts'), content, 'utf-8');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function getAvailableDatasources() {
|
|
28
|
-
try {
|
|
29
|
-
const files = await fs.readdir(CORE_DATASOURCES_PATH);
|
|
30
|
-
return files
|
|
31
|
-
.filter(f => f.endsWith('.ts') && !f.startsWith('_'))
|
|
32
|
-
.map(f => f.replace('.ts', ''));
|
|
33
|
-
} catch {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export default async function generateCrud() {
|
|
39
|
-
const config = await loadUserConfig();
|
|
40
|
-
const datasources = await getAvailableDatasources();
|
|
41
|
-
|
|
42
|
-
const answers = await prompts([
|
|
43
|
-
config.modules?.length
|
|
44
|
-
? {
|
|
45
|
-
type: 'select',
|
|
46
|
-
name: 'module',
|
|
47
|
-
message: 'Select target module:',
|
|
48
|
-
choices: [...config.modules, 'shared-context'].map(m => ({
|
|
49
|
-
title: m === 'shared-context' ? 'shared-context (shared)' : m,
|
|
50
|
-
value: m
|
|
51
|
-
}))
|
|
52
|
-
}
|
|
53
|
-
: null,
|
|
54
|
-
{ type: 'text', name: 'className', message: 'Class name (PascalCase):' },
|
|
55
|
-
{ type: 'text', name: 'endpoint', message: 'API endpoint:' },
|
|
56
|
-
{
|
|
57
|
-
type: 'select',
|
|
58
|
-
name: 'datasource',
|
|
59
|
-
message: 'Select datasource:',
|
|
60
|
-
choices: datasources.map(d => ({ title: d, value: d }))
|
|
61
|
-
}
|
|
62
|
-
].filter(Boolean));
|
|
63
|
-
|
|
64
|
-
const rawName = answers.className.trim();
|
|
65
|
-
const capital = rawName[0].toUpperCase() + rawName.slice(1);
|
|
66
|
-
const lower = rawName[0].toLowerCase() + rawName.slice(1);
|
|
67
|
-
const endpoint = answers.endpoint.trim();
|
|
68
|
-
const datasource = answers.datasource.trim();
|
|
69
|
-
const selectedModule = answers.module;
|
|
70
|
-
|
|
71
|
-
const baseArch = path.join(process.cwd(), 'src/app/contexts');
|
|
72
|
-
const basePath = selectedModule ? path.join(baseArch, selectedModule) : path.join(baseArch, 'app-context');
|
|
73
|
-
|
|
74
|
-
const domainPath = path.join(basePath, 'Domain', capital);
|
|
75
|
-
const serviceDir = path.join(basePath, 'Application', 'Services');
|
|
76
|
-
const repoPath = path.join(basePath, 'Infra', 'Repo');
|
|
77
|
-
|
|
78
|
-
const aliasCurrent = selectedModule === 'shared-context'
|
|
79
|
-
? '$shared-context'
|
|
80
|
-
: selectedModule
|
|
81
|
-
? `$${selectedModule}`
|
|
82
|
-
: '$app-context';
|
|
83
|
-
const aliasShared = '$shared-context';
|
|
84
|
-
|
|
85
|
-
const templateData = {
|
|
86
|
-
camelCaseName: lower,
|
|
87
|
-
pascalCaseName: capital,
|
|
88
|
-
endpoint,
|
|
89
|
-
datasource,
|
|
90
|
-
aliasDomain: `${aliasCurrent}/Domain`,
|
|
91
|
-
aliasShared,
|
|
92
|
-
aliasApplication: `${aliasCurrent}/Application`,
|
|
93
|
-
aliasInfra: `${aliasCurrent}/Infra`,
|
|
94
|
-
aliasRepo: `${aliasCurrent}/Infra/Repo`
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// === 1) Abstracts
|
|
98
|
-
const abstractsDir = path.join(domainPath, 'Abstracts');
|
|
99
|
-
for (const file of ['Class', 'ClassCreateRequest', 'ClassUpdateRequest', 'ClassCollectionQuery']) {
|
|
100
|
-
const tpl = await fs.readFile(path.join(__dirname, `../templates/domain/abstracts/${file}.ejs`), 'utf-8');
|
|
101
|
-
const result = ejs.render(tpl, templateData);
|
|
102
|
-
await writeIfNotExists(path.join(abstractsDir, `${capital}${file.replace('Class', '')}.ts`), result);
|
|
103
|
-
}
|
|
104
|
-
await updateIndexTs(abstractsDir);
|
|
105
|
-
|
|
106
|
-
// === 2) Request
|
|
107
|
-
const requestDir = path.join(domainPath, 'Request');
|
|
108
|
-
for (const file of ['ClassCreateRequest', 'ClassUpdateRequest']) {
|
|
109
|
-
const tpl = await fs.readFile(path.join(__dirname, `../templates/domain/request/${file}.ejs`), 'utf-8');
|
|
110
|
-
const result = ejs.render(tpl, templateData);
|
|
111
|
-
await writeIfNotExists(path.join(requestDir, `${capital}${file.replace('Class', '')}.ts`), result);
|
|
112
|
-
}
|
|
113
|
-
await updateIndexTs(requestDir);
|
|
114
|
-
|
|
115
|
-
// === 3) Services
|
|
116
|
-
const serviceTpl = await fs.readFile(path.join(__dirname, '../templates/domain/services/ClassService.ejs'), 'utf-8');
|
|
117
|
-
const serviceResult = ejs.render(serviceTpl, templateData);
|
|
118
|
-
await writeIfNotExists(path.join(serviceDir, `${capital}Service.ts`), serviceResult);
|
|
119
|
-
await updateIndexTs(serviceDir);
|
|
120
|
-
|
|
121
|
-
// === 4) Domain Index
|
|
122
|
-
await writeIfNotExists(
|
|
123
|
-
path.join(domainPath, 'index.ts'),
|
|
124
|
-
[`export * from './Abstracts';`, `export * from './Request';`].join('\n')
|
|
125
|
-
);
|
|
126
|
-
//await updateIndexTs(domainPath);
|
|
127
|
-
|
|
128
|
-
// === 5) Repo
|
|
129
|
-
const repoTpl = await fs.readFile(path.join(__dirname, '../templates/data/repo/ClassRepo.ejs'), 'utf-8');
|
|
130
|
-
const repoResult = ejs.render(repoTpl, templateData);
|
|
131
|
-
await writeIfNotExists(path.join(repoPath, `${capital}Repo.ts`), repoResult);
|
|
132
|
-
await updateIndexTs(repoPath);
|
|
133
|
-
|
|
134
|
-
console.log(`✅ CRUD for ${capital} generated in ${selectedModule || 'app-context'} structure`);
|
|
135
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import prompts from 'prompts';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
const CORE_DATASOURCES_PATH = path.join(process.cwd(), 'src/app/core/datasources');
|
|
6
|
-
|
|
7
|
-
async function writeIfNotExists(filepath, content) {
|
|
8
|
-
try {
|
|
9
|
-
await fs.access(filepath);
|
|
10
|
-
console.log(`⚠️ Skip existing: ${filepath}`);
|
|
11
|
-
} catch {
|
|
12
|
-
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
|
13
|
-
await fs.writeFile(filepath, content, 'utf-8');
|
|
14
|
-
console.log(`✅ Created: ${filepath}`);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function updateIndexTs(dir) {
|
|
19
|
-
const files = (await fs.readdir(dir)).filter(f => f.endsWith('.ts') && f !== 'index.ts');
|
|
20
|
-
const content = files.map(f => `export * from './${f.replace('.ts', '')}';`).join('\n') + '\n';
|
|
21
|
-
await fs.writeFile(path.join(dir, 'index.ts'), content, 'utf-8');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export default async function generateDatasource() {
|
|
25
|
-
const { name } = await prompts({
|
|
26
|
-
type: 'text',
|
|
27
|
-
name: 'name',
|
|
28
|
-
message: 'Datasource class name (PascalCase, e.g., BackendApi):'
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const capital = name.trim();
|
|
32
|
-
const filePath = path.join(CORE_DATASOURCES_PATH, `${capital}Datasource.ts`);
|
|
33
|
-
|
|
34
|
-
const content = `import { BaseHttpDatasource, HttpService } from '@azure-net/kit';
|
|
35
|
-
|
|
36
|
-
export class ${capital}Datasource extends BaseHttpDatasource {
|
|
37
|
-
constructor() {
|
|
38
|
-
super({ http: new HttpService({ baseUrl: '' }) }); // TODO: fill generated class
|
|
39
|
-
}
|
|
40
|
-
}`;
|
|
41
|
-
|
|
42
|
-
await writeIfNotExists(filePath, content);
|
|
43
|
-
await updateIndexTs(CORE_DATASOURCES_PATH);
|
|
44
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import prompts from 'prompts';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
5
|
-
|
|
6
|
-
const CORE_DATASOURCES_PATH = path.join(process.cwd(), 'src/app/core/datasources');
|
|
7
|
-
const CONTEXTS_PATH = path.join(process.cwd(), 'src/app/contexts');
|
|
8
|
-
|
|
9
|
-
async function getAvailableDatasources() {
|
|
10
|
-
try {
|
|
11
|
-
const files = await fs.readdir(CORE_DATASOURCES_PATH);
|
|
12
|
-
return files.filter(f => f.endsWith('.ts') && !f.startsWith('_')).map(f => f.replace('.ts', ''));
|
|
13
|
-
} catch {
|
|
14
|
-
return [];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function updateIndexTs(dir) {
|
|
19
|
-
const files = (await fs.readdir(dir)).filter(f => f.endsWith('.ts') && f !== 'index.ts');
|
|
20
|
-
const content = files.map(f => `export * from './${f.replace('.ts', '')}';`).join('\n') + '\n';
|
|
21
|
-
await fs.writeFile(path.join(dir, 'index.ts'), content, 'utf-8');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export default async function generateRepo() {
|
|
25
|
-
const config = await loadUserConfig();
|
|
26
|
-
const datasources = await getAvailableDatasources();
|
|
27
|
-
|
|
28
|
-
const answers = await prompts([
|
|
29
|
-
config.modules?.length ? {
|
|
30
|
-
type: 'select',
|
|
31
|
-
name: 'module',
|
|
32
|
-
message: 'Select target module:',
|
|
33
|
-
choices: [...config.modules, 'shared-context'].map(m => ({ title: m, value: m }))
|
|
34
|
-
} : null,
|
|
35
|
-
{ type: 'text', name: 'name', message: 'Repository class name (PascalCase):' },
|
|
36
|
-
{
|
|
37
|
-
type: 'select',
|
|
38
|
-
name: 'datasource',
|
|
39
|
-
message: 'Select datasource:',
|
|
40
|
-
choices: datasources.map(d => ({ title: d, value: d }))
|
|
41
|
-
}
|
|
42
|
-
].filter(Boolean));
|
|
43
|
-
|
|
44
|
-
const { name, module, datasource } = answers;
|
|
45
|
-
const capital = name.trim();
|
|
46
|
-
|
|
47
|
-
const basePath = path.join(CONTEXTS_PATH, module || 'app-context');
|
|
48
|
-
const repoDir = path.join(basePath, 'Infra', 'Repo');
|
|
49
|
-
const filePath = path.join(repoDir, `${capital}Repo.ts`);
|
|
50
|
-
|
|
51
|
-
const aliasRepo = module === 'shared-context'
|
|
52
|
-
? '$shared-context/Infra/Repo'
|
|
53
|
-
: `$${module}/Infra/Repo`;
|
|
54
|
-
|
|
55
|
-
const content = `import { ${datasource} } from '$core/datasources';
|
|
56
|
-
|
|
57
|
-
export class ${capital}Repo {
|
|
58
|
-
private source = new ${datasource}();
|
|
59
|
-
|
|
60
|
-
constructor() {
|
|
61
|
-
// TODO: fill generated class
|
|
62
|
-
}
|
|
63
|
-
}`;
|
|
64
|
-
|
|
65
|
-
await fs.mkdir(repoDir, { recursive: true });
|
|
66
|
-
await fs.writeFile(filePath, content);
|
|
67
|
-
await updateIndexTs(repoDir);
|
|
68
|
-
console.log(`✅ Repo created at ${filePath}`);
|
|
69
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import prompts from 'prompts';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
5
|
-
|
|
6
|
-
const CONTEXTS_PATH = path.join(process.cwd(), 'src/app/contexts');
|
|
7
|
-
|
|
8
|
-
async function getAvailableRepos(module) {
|
|
9
|
-
const repoPath = path.join(CONTEXTS_PATH, module, 'Infra', 'Repo');
|
|
10
|
-
try {
|
|
11
|
-
const files = await fs.readdir(repoPath);
|
|
12
|
-
return ['Without repo', ...files.filter(f => f.endsWith('.ts') && f !== 'index.ts').map(f => f.replace('.ts', ''))];
|
|
13
|
-
} catch {
|
|
14
|
-
return ['Without repo'];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function updateIndexTs(dir) {
|
|
19
|
-
const files = (await fs.readdir(dir)).filter(f => f.endsWith('.ts') && f !== 'index.ts');
|
|
20
|
-
const content = files.map(f => `export * from './${f.replace('.ts', '')}';`).join('\n') + '\n';
|
|
21
|
-
await fs.writeFile(path.join(dir, 'index.ts'), content, 'utf-8');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export default async function generateService() {
|
|
25
|
-
const config = await loadUserConfig();
|
|
26
|
-
|
|
27
|
-
const { module } = await prompts({
|
|
28
|
-
type: 'select',
|
|
29
|
-
name: 'module',
|
|
30
|
-
message: 'Select target module:',
|
|
31
|
-
choices: [...config.modules, 'shared-context'].map(m => ({ title: m, value: m }))
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const { name } = await prompts({
|
|
35
|
-
type: 'text',
|
|
36
|
-
name: 'name',
|
|
37
|
-
message: 'Service class name (PascalCase):'
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const repos = await getAvailableRepos(module);
|
|
41
|
-
const { repo } = await prompts({
|
|
42
|
-
type: 'select',
|
|
43
|
-
name: 'repo',
|
|
44
|
-
message: 'Select repository:',
|
|
45
|
-
choices: repos.map(r => ({ title: r, value: r }))
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const capital = name.trim();
|
|
49
|
-
const basePath = path.join(CONTEXTS_PATH, module || 'app-context');
|
|
50
|
-
const serviceDir = path.join(basePath, 'Application', 'Services');
|
|
51
|
-
const filePath = path.join(serviceDir, `${capital}Service.ts`);
|
|
52
|
-
|
|
53
|
-
const aliasRepo = module === 'shared-context'
|
|
54
|
-
? '$shared-context/Infra/Repo'
|
|
55
|
-
: `$${module}/Infra/Repo`;
|
|
56
|
-
|
|
57
|
-
const content = repo === 'Without repo'
|
|
58
|
-
? `export class ${capital}Service {
|
|
59
|
-
constructor() {
|
|
60
|
-
// TODO: fill generated class
|
|
61
|
-
}
|
|
62
|
-
}`
|
|
63
|
-
: `import { ${repo} } from '${aliasRepo}';
|
|
64
|
-
|
|
65
|
-
export class ${capital}Service {
|
|
66
|
-
private repo = new ${repo}();
|
|
67
|
-
|
|
68
|
-
constructor() {
|
|
69
|
-
// TODO: fill generated class
|
|
70
|
-
}
|
|
71
|
-
}`;
|
|
72
|
-
|
|
73
|
-
await fs.mkdir(serviceDir, { recursive: true });
|
|
74
|
-
await fs.writeFile(filePath, content);
|
|
75
|
-
await updateIndexTs(serviceDir);
|
|
76
|
-
console.log(`✅ Service created at ${filePath}`);
|
|
77
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
4
|
-
|
|
5
|
-
const BASE_STRUCTURE = ['Domain', 'Infra', 'Application', 'Presentation'];
|
|
6
|
-
const APP_ROOT = path.join(process.cwd(), 'src', 'app');
|
|
7
|
-
const CONTEXTS_PATH = path.join(APP_ROOT, 'contexts');
|
|
8
|
-
const CORE_PATH = path.join(APP_ROOT, 'core');
|
|
9
|
-
|
|
10
|
-
async function createDirIfNotExists(dirPath) {
|
|
11
|
-
try {
|
|
12
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
13
|
-
console.log(`📁 Created: ${dirPath}`);
|
|
14
|
-
} catch (e) {
|
|
15
|
-
console.error(`❌ Failed to create ${dirPath}:`, e.message);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function createBaseStructure(root) {
|
|
20
|
-
for (const section of BASE_STRUCTURE) {
|
|
21
|
-
const sectionPath = path.join(root, section);
|
|
22
|
-
await createDirIfNotExists(sectionPath);
|
|
23
|
-
|
|
24
|
-
if (section === 'Infra') {
|
|
25
|
-
await createDirIfNotExists(path.join(sectionPath, 'Repo'));
|
|
26
|
-
}
|
|
27
|
-
if (section === 'Application') {
|
|
28
|
-
await createDirIfNotExists(path.join(sectionPath, 'Services'));
|
|
29
|
-
await createDirIfNotExists(path.join(sectionPath, 'Orchestrators'));
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default async function initStructure() {
|
|
35
|
-
const config = await loadUserConfig();
|
|
36
|
-
const modules = config.modules;
|
|
37
|
-
const hasModules = Array.isArray(modules) && modules.length > 0;
|
|
38
|
-
|
|
39
|
-
console.log('📦 Generating architecture in: /contexts');
|
|
40
|
-
|
|
41
|
-
// всегда shared-context
|
|
42
|
-
const sharedKernelPath = path.join(CONTEXTS_PATH, 'shared-context');
|
|
43
|
-
await createBaseStructure(sharedKernelPath);
|
|
44
|
-
|
|
45
|
-
if (hasModules) {
|
|
46
|
-
// по модулям
|
|
47
|
-
console.log('🔧 Creating module contexts...');
|
|
48
|
-
for (const mod of modules) {
|
|
49
|
-
const modPath = path.join(CONTEXTS_PATH, mod);
|
|
50
|
-
await createBaseStructure(modPath);
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
// fallback-контекст
|
|
54
|
-
console.log('🧱 No modules found — using default "app-context"...');
|
|
55
|
-
const fallbackPath = path.join(CONTEXTS_PATH, 'app-context');
|
|
56
|
-
await createBaseStructure(fallbackPath);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Доп. слои вне contexts
|
|
60
|
-
await createDirIfNotExists(CORE_PATH);
|
|
61
|
-
await createDirIfNotExists(path.join(CORE_PATH, 'datasources'));
|
|
62
|
-
await createDirIfNotExists(path.join(APP_ROOT, 'ui'));
|
|
63
|
-
|
|
64
|
-
console.log('✅ Structure initialization complete!');
|
|
65
|
-
}
|