@azure-net/cli 2.3.62 → 4.0.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/.prettierrc +7 -0
- package/bin/azure-net.js +247 -157
- package/bin/azure-net.ts +253 -0
- package/eslint.config.js +19 -0
- package/package.json +12 -5
- package/src/commands/generate/generateCrudBase.js +95 -114
- package/src/commands/generate/generateCrudPresenter.js +135 -142
- package/src/commands/generate/generateModule.js +21 -21
- package/src/commands/generate/generateModuleBase.js +92 -114
- package/src/commands/generate/generateModulePresenter.js +73 -80
- package/src/commands/generate/generatePresenter.js +38 -44
- package/src/commands/generate/generateRepository.js +41 -47
- package/src/commands/generate/generateSchema.js +74 -70
- package/src/commands/generate/generateService.js +32 -37
- package/src/commands/generate/index.js +0 -5
- package/src/generate/layers/generateRepository.js +124 -0
- package/src/generate/layers/generateRepositoryMethod.js +102 -0
- package/src/generate/layers/generateUseCases.js +50 -0
- package/src/generate/layers/repositoryShared.js +569 -0
- package/src/generate/module/generateDomain.js +146 -0
- package/src/generate/module/generateDomain.ts +147 -0
- package/src/generate/module/generateModule.js +84 -0
- package/src/generate/module/generateModule.ts +84 -0
- package/src/generate/module/generatePresenter.js +419 -0
- package/src/generate/module/generatePresenter.ts +442 -0
- package/src/generate/module/generateRepository.js +144 -0
- package/src/generate/module/generateRepository.ts +158 -0
- package/src/generate/module/generateSchemaFromType.js +320 -0
- package/src/generate/module/generateSchemaFromType.ts +325 -0
- package/src/generate/module/generateTypesJson.js +252 -0
- package/src/generate/module/generateTypesJson.ts +259 -0
- package/src/generate/module/generateUseCases.js +67 -0
- package/src/generate/module/generateUseCases.ts +67 -0
- package/src/generate/module/repositoryModuleShared.js +598 -0
- package/src/generate/ui/generateComponent.js +98 -0
- package/src/generate/ui/generateComponent.ts +100 -0
- package/src/generate/ui/generateDesignComponent.js +71 -0
- package/src/generate/ui/generateDesignComponent.ts +71 -0
- package/src/generate/ui/generateWidget.js +71 -0
- package/src/generate/ui/generateWidget.ts +71 -0
- package/src/init/initAliases.js +159 -0
- package/src/init/initAliases.ts +167 -0
- package/src/init/initEdges.js +74 -0
- package/src/init/initEdges.ts +74 -0
- package/src/init/initStructure.js +122 -0
- package/src/init/initStructure.ts +124 -0
- package/src/install/installFresh.js +101 -0
- package/src/install/installFresh.ts +101 -0
- package/src/plugins/addLint.js +257 -0
- package/src/plugins/addLint.ts +274 -0
- package/src/plugins/index.js +1 -0
- package/src/plugins/index.ts +1 -0
- package/src/scaffolds/createCoreMiddleware.js +27 -0
- package/src/scaffolds/createCoreMiddleware.ts +27 -0
- package/src/scaffolds/createCoreSchema.js +141 -0
- package/src/scaffolds/createCoreSchema.ts +142 -0
- package/src/scaffolds/createDatasource.js +173 -0
- package/src/scaffolds/createDatasource.ts +175 -0
- package/src/scaffolds/createDatasourceProvider.js +164 -0
- package/src/scaffolds/createDatasourceProvider.ts +175 -0
- package/src/scaffolds/createMiddleware.js +105 -0
- package/src/scaffolds/createMiddleware.ts +105 -0
- package/src/scaffolds/createPresenterFactory.js +112 -0
- package/src/scaffolds/createPresenterFactory.ts +114 -0
- package/src/scaffolds/createResponse.js +64 -0
- package/src/scaffolds/createResponse.ts +64 -0
- package/src/scaffolds/createSchemaRule.js +40 -0
- package/src/scaffolds/createSchemaRule.ts +42 -0
- package/src/scaffolds/createTranslationManager.js +78 -0
- package/src/scaffolds/createTranslationManager.ts +78 -0
- package/src/scaffolds/index.js +9 -0
- package/src/scaffolds/index.ts +9 -0
- package/src/utils/contextUtils.js +56 -49
- package/src/utils/fileUtils.js +72 -84
- package/src/utils/loadConfig.js +57 -13
- package/tsconfig.json +19 -0
- package/src/commands/generate/generateComponent.js +0 -53
- package/src/commands/generate/generateDatasource.js +0 -84
- package/src/commands/generate/generateDesignComponent.js +0 -53
- package/src/commands/generate/generateResponse.js +0 -41
- package/src/commands/generate/generateWidget.js +0 -53
- package/src/commands/init/index.js +0 -9
- package/src/commands/init/initAliases.js +0 -52
- package/src/commands/init/initEdges.js +0 -42
- package/src/commands/init/initLint.js +0 -138
- package/src/commands/init/initMiddleware.js +0 -21
- package/src/commands/init/initPresenter.js +0 -25
- package/src/commands/init/initSchema.js +0 -22
- package/src/commands/init/initStructure.js +0 -140
- package/src/commands/init/initTranslations.js +0 -59
- package/src/commands/init/migrateStructure.js +0 -144
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
|
|
6
|
+
import { toPascalCase } from '../utils/contextUtils.js';
|
|
7
|
+
|
|
8
|
+
const resolvePresetImportName = (preset) => {
|
|
9
|
+
switch (preset) {
|
|
10
|
+
case 'ru':
|
|
11
|
+
return 'validationMessagesRu';
|
|
12
|
+
case 'en':
|
|
13
|
+
return 'validationMessagesEn';
|
|
14
|
+
case 'i18n':
|
|
15
|
+
return 'validationMessagesI18n';
|
|
16
|
+
default:
|
|
17
|
+
return 'validationMessagesI18n';
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const createSchemaFactoryTemplate = (
|
|
22
|
+
factoryName,
|
|
23
|
+
messagesImportName
|
|
24
|
+
) => `import { createSchemaFactory, createRules, ${messagesImportName} } from '@azure-net/kit/delivery';
|
|
25
|
+
|
|
26
|
+
export const ${factoryName} = createSchemaFactory(createRules(${messagesImportName}));
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const resolveConfigFile = (cwd) => {
|
|
30
|
+
const tsPath = path.join(cwd, 'azure-net.config.ts');
|
|
31
|
+
const jsPath = path.join(cwd, 'azure-net.config.js');
|
|
32
|
+
|
|
33
|
+
if (fsSync.existsSync(tsPath)) {
|
|
34
|
+
return { filepath: tsPath, type: 'ts', exists: true, content: fsSync.readFileSync(tsPath, 'utf-8') };
|
|
35
|
+
}
|
|
36
|
+
if (fsSync.existsSync(jsPath)) {
|
|
37
|
+
return { filepath: jsPath, type: 'js', exists: true, content: fsSync.readFileSync(jsPath, 'utf-8') };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { filepath: tsPath, type: 'ts', exists: false, content: '' };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const buildDefaultSchemaFactoryConfigEntry = (factoryName) => {
|
|
44
|
+
return `defaultSchemaFactory: '${factoryName}'`;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const upsertDefaultSchemaFactoryConfig = (content, factoryName) => {
|
|
48
|
+
const hasExportDefaultObject = /export\s+default\s*\{[\s\S]*\}\s*;?/.test(content);
|
|
49
|
+
const defaultFactoryEntry = buildDefaultSchemaFactoryConfigEntry(factoryName);
|
|
50
|
+
const coreAliasEntry = `coreAlias: '$core'`;
|
|
51
|
+
|
|
52
|
+
if (!hasExportDefaultObject) {
|
|
53
|
+
return `export default {\n\t${coreAliasEntry},\n\t${defaultFactoryEntry}\n};\n`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let nextContent = content;
|
|
57
|
+
|
|
58
|
+
if (/defaultSchemaFactory\s*:/.test(nextContent)) {
|
|
59
|
+
nextContent = nextContent.replace(/defaultSchemaFactory\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
|
|
60
|
+
} else if (/defaultSchema\s*:/.test(nextContent)) {
|
|
61
|
+
nextContent = nextContent.replace(/defaultSchema\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
|
|
62
|
+
} else {
|
|
63
|
+
nextContent = nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body) => {
|
|
64
|
+
const trimmedRight = body.replace(/\s*$/, '');
|
|
65
|
+
if (!trimmedRight.length) {
|
|
66
|
+
return `export default {\n\t${defaultFactoryEntry}\n};`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
|
|
70
|
+
return `export default {${withComma}\n\t${defaultFactoryEntry}\n};`;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (/coreAlias\s*:/.test(nextContent)) {
|
|
75
|
+
return nextContent.replace(/coreAlias\s*:\s*['"`][^'"`]+['"`]/, coreAliasEntry);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body) => {
|
|
79
|
+
const trimmedRight = body.replace(/\s*$/, '');
|
|
80
|
+
if (!trimmedRight.length) {
|
|
81
|
+
return `export default {\n\t${coreAliasEntry}\n};`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
|
|
85
|
+
return `export default {${withComma}\n\t${coreAliasEntry}\n};`;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default async function createCoreSchema() {
|
|
90
|
+
const { schemaFactoryNameRaw } = await prompts({
|
|
91
|
+
type: 'text',
|
|
92
|
+
name: 'schemaFactoryNameRaw',
|
|
93
|
+
message: 'Schema factory name:',
|
|
94
|
+
initial: 'Schema'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const schemaFactoryName = toPascalCase(String(schemaFactoryNameRaw ?? 'Schema')) || 'Schema';
|
|
98
|
+
|
|
99
|
+
const { preset } = await prompts({
|
|
100
|
+
type: 'select',
|
|
101
|
+
name: 'preset',
|
|
102
|
+
message: 'Choose validation messages:',
|
|
103
|
+
choices: [
|
|
104
|
+
{ title: 'Russian', value: 'ru' },
|
|
105
|
+
{ title: 'English', value: 'en' },
|
|
106
|
+
{ title: 'i18n', value: 'i18n' }
|
|
107
|
+
],
|
|
108
|
+
initial: 2
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const selectedPreset = preset ?? 'i18n';
|
|
112
|
+
const messagesImportName = resolvePresetImportName(selectedPreset);
|
|
113
|
+
const coreSchemaPath = path.join(process.cwd(), 'src', 'core', 'schema');
|
|
114
|
+
const customRulesPath = path.join(coreSchemaPath, 'custom-rules');
|
|
115
|
+
const filePath = path.join(coreSchemaPath, `${schemaFactoryName}.ts`);
|
|
116
|
+
|
|
117
|
+
await fs.mkdir(coreSchemaPath, { recursive: true });
|
|
118
|
+
await fs.mkdir(customRulesPath, { recursive: true });
|
|
119
|
+
|
|
120
|
+
const created = await writeIfNotExists(filePath, createSchemaFactoryTemplate(schemaFactoryName, messagesImportName));
|
|
121
|
+
if (!created) {
|
|
122
|
+
console.log(`⚠️ Schema factory "${schemaFactoryName}" already exists. File was not overwritten.`);
|
|
123
|
+
} else {
|
|
124
|
+
await updateIndexTs(coreSchemaPath);
|
|
125
|
+
console.log(`✅ Core schema factory created: ${filePath}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { useAsDefault } = await prompts({
|
|
129
|
+
type: 'confirm',
|
|
130
|
+
name: 'useAsDefault',
|
|
131
|
+
message: 'Use this schema factory as default for the project?',
|
|
132
|
+
initial: true
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!useAsDefault) return;
|
|
136
|
+
|
|
137
|
+
const config = resolveConfigFile(process.cwd());
|
|
138
|
+
const nextContent = upsertDefaultSchemaFactoryConfig(config.content, schemaFactoryName);
|
|
139
|
+
await fs.writeFile(config.filepath, nextContent, 'utf-8');
|
|
140
|
+
console.log(`✅ Default schema factory saved to ${path.basename(config.filepath)}`);
|
|
141
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
|
|
6
|
+
import { toPascalCase } from '../utils/contextUtils.js';
|
|
7
|
+
|
|
8
|
+
type MessagePreset = 'ru' | 'en' | 'i18n';
|
|
9
|
+
type ConfigPath = 'ts' | 'js';
|
|
10
|
+
|
|
11
|
+
const resolvePresetImportName = (preset: MessagePreset): string => {
|
|
12
|
+
switch (preset) {
|
|
13
|
+
case 'ru':
|
|
14
|
+
return 'validationMessagesRu';
|
|
15
|
+
case 'en':
|
|
16
|
+
return 'validationMessagesEn';
|
|
17
|
+
case 'i18n':
|
|
18
|
+
return 'validationMessagesI18n';
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const createSchemaFactoryTemplate = (
|
|
23
|
+
factoryName: string,
|
|
24
|
+
messagesImportName: string
|
|
25
|
+
): string => `import { createSchemaFactory, createRules, ${messagesImportName} } from '@azure-net/kit/delivery';
|
|
26
|
+
|
|
27
|
+
export const ${factoryName} = createSchemaFactory(createRules(${messagesImportName}));
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const resolveConfigFile = (cwd: string): { filepath: string; type: ConfigPath; exists: boolean; content: string } => {
|
|
31
|
+
const tsPath = path.join(cwd, 'azure-net.config.ts');
|
|
32
|
+
const jsPath = path.join(cwd, 'azure-net.config.js');
|
|
33
|
+
|
|
34
|
+
if (fsSync.existsSync(tsPath)) {
|
|
35
|
+
return { filepath: tsPath, type: 'ts', exists: true, content: fsSync.readFileSync(tsPath, 'utf-8') };
|
|
36
|
+
}
|
|
37
|
+
if (fsSync.existsSync(jsPath)) {
|
|
38
|
+
return { filepath: jsPath, type: 'js', exists: true, content: fsSync.readFileSync(jsPath, 'utf-8') };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { filepath: tsPath, type: 'ts', exists: false, content: '' };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const buildDefaultSchemaFactoryConfigEntry = (factoryName: string): string => {
|
|
45
|
+
return `defaultSchemaFactory: '${factoryName}'`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const upsertDefaultSchemaFactoryConfig = (content: string, factoryName: string): string => {
|
|
49
|
+
const hasExportDefaultObject = /export\s+default\s*\{[\s\S]*\}\s*;?/.test(content);
|
|
50
|
+
const defaultFactoryEntry = buildDefaultSchemaFactoryConfigEntry(factoryName);
|
|
51
|
+
const coreAliasEntry = `coreAlias: '$core'`;
|
|
52
|
+
|
|
53
|
+
if (!hasExportDefaultObject) {
|
|
54
|
+
return `export default {\n\t${coreAliasEntry},\n\t${defaultFactoryEntry}\n};\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let nextContent = content;
|
|
58
|
+
|
|
59
|
+
if (/defaultSchemaFactory\s*:/.test(nextContent)) {
|
|
60
|
+
nextContent = nextContent.replace(/defaultSchemaFactory\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
|
|
61
|
+
} else if (/defaultSchema\s*:/.test(nextContent)) {
|
|
62
|
+
nextContent = nextContent.replace(/defaultSchema\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
|
|
63
|
+
} else {
|
|
64
|
+
nextContent = nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body: string) => {
|
|
65
|
+
const trimmedRight = body.replace(/\s*$/, '');
|
|
66
|
+
if (!trimmedRight.length) {
|
|
67
|
+
return `export default {\n\t${defaultFactoryEntry}\n};`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
|
|
71
|
+
return `export default {${withComma}\n\t${defaultFactoryEntry}\n};`;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (/coreAlias\s*:/.test(nextContent)) {
|
|
76
|
+
return nextContent.replace(/coreAlias\s*:\s*['"`][^'"`]+['"`]/, coreAliasEntry);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body: string) => {
|
|
80
|
+
const trimmedRight = body.replace(/\s*$/, '');
|
|
81
|
+
if (!trimmedRight.length) {
|
|
82
|
+
return `export default {\n\t${coreAliasEntry}\n};`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
|
|
86
|
+
return `export default {${withComma}\n\t${coreAliasEntry}\n};`;
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default async function createCoreSchema(): Promise<void> {
|
|
91
|
+
const { schemaFactoryNameRaw } = await prompts({
|
|
92
|
+
type: 'text',
|
|
93
|
+
name: 'schemaFactoryNameRaw',
|
|
94
|
+
message: 'Schema factory name:',
|
|
95
|
+
initial: 'Schema'
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const schemaFactoryName = toPascalCase(String(schemaFactoryNameRaw ?? 'Schema')) || 'Schema';
|
|
99
|
+
|
|
100
|
+
const { preset } = await prompts({
|
|
101
|
+
type: 'select',
|
|
102
|
+
name: 'preset',
|
|
103
|
+
message: 'Choose validation messages:',
|
|
104
|
+
choices: [
|
|
105
|
+
{ title: 'Russian', value: 'ru' },
|
|
106
|
+
{ title: 'English', value: 'en' },
|
|
107
|
+
{ title: 'i18n', value: 'i18n' }
|
|
108
|
+
],
|
|
109
|
+
initial: 2
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const selectedPreset = (preset ?? 'i18n') as MessagePreset;
|
|
113
|
+
const messagesImportName = resolvePresetImportName(selectedPreset);
|
|
114
|
+
const coreSchemaPath = path.join(process.cwd(), 'src', 'core', 'schema');
|
|
115
|
+
const customRulesPath = path.join(coreSchemaPath, 'custom-rules');
|
|
116
|
+
const filePath = path.join(coreSchemaPath, `${schemaFactoryName}.ts`);
|
|
117
|
+
|
|
118
|
+
await fs.mkdir(coreSchemaPath, { recursive: true });
|
|
119
|
+
await fs.mkdir(customRulesPath, { recursive: true });
|
|
120
|
+
|
|
121
|
+
const created = await writeIfNotExists(filePath, createSchemaFactoryTemplate(schemaFactoryName, messagesImportName));
|
|
122
|
+
if (!created) {
|
|
123
|
+
console.log(`⚠️ Schema factory "${schemaFactoryName}" already exists. File was not overwritten.`);
|
|
124
|
+
} else {
|
|
125
|
+
await updateIndexTs(coreSchemaPath);
|
|
126
|
+
console.log(`✅ Core schema factory created: ${filePath}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { useAsDefault } = await prompts({
|
|
130
|
+
type: 'confirm',
|
|
131
|
+
name: 'useAsDefault',
|
|
132
|
+
message: 'Use this schema factory as default for the project?',
|
|
133
|
+
initial: true
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!useAsDefault) return;
|
|
137
|
+
|
|
138
|
+
const config = resolveConfigFile(process.cwd());
|
|
139
|
+
const nextContent = upsertDefaultSchemaFactoryConfig(config.content, schemaFactoryName);
|
|
140
|
+
await fs.writeFile(config.filepath, nextContent, 'utf-8');
|
|
141
|
+
console.log(`✅ Default schema factory saved to ${path.basename(config.filepath)}`);
|
|
142
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
|
|
5
|
+
import { toPascalCase } from '../utils/contextUtils.js';
|
|
6
|
+
import { loadUserConfig, resolveConfigFile } from '../utils/loadConfig.js';
|
|
7
|
+
import { normalizeContexts } from '../init/initAliases.js';
|
|
8
|
+
|
|
9
|
+
const createDatasourceTemplateWithoutResponse = (
|
|
10
|
+
className
|
|
11
|
+
) => `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
12
|
+
|
|
13
|
+
export class ${className} extends BaseHttpDatasource {
|
|
14
|
+
\tasync createRequest<T>(callback: CreateRequestCallbackType<T>) {
|
|
15
|
+
\t\treturn await this.createRawRequest<T>(callback);
|
|
16
|
+
\t}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const createDatasourceTemplateWithResponseAndInterface = (
|
|
21
|
+
className,
|
|
22
|
+
responseName,
|
|
23
|
+
coreAlias
|
|
24
|
+
) => `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
25
|
+
import { ${responseName}, type I${responseName} } from '${coreAlias}/response';
|
|
26
|
+
|
|
27
|
+
export class ${className} extends BaseHttpDatasource {
|
|
28
|
+
\tasync createRequest<T>(callback: CreateRequestCallbackType<I${responseName}<T>>) {
|
|
29
|
+
\t\treturn new ${responseName}<T>(await this.createRawRequest<I${responseName}<T>>(callback));
|
|
30
|
+
\t}
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const createDatasourceTemplateWithResponse = (
|
|
35
|
+
className,
|
|
36
|
+
responseName,
|
|
37
|
+
coreAlias
|
|
38
|
+
) => `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
39
|
+
import { ${responseName} } from '${coreAlias}/response';
|
|
40
|
+
|
|
41
|
+
export class ${className} extends BaseHttpDatasource {
|
|
42
|
+
\tasync createRequest<T>(callback: CreateRequestCallbackType<T>) {
|
|
43
|
+
\t\treturn new ${responseName}<T>(await this.createRawRequest<T>(callback));
|
|
44
|
+
\t}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const ensureDatasourceClassName = (rawName) => {
|
|
49
|
+
const normalized = toPascalCase(rawName || 'MockApi');
|
|
50
|
+
return normalized.endsWith('Datasource') ? normalized : `${normalized}Datasource`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getContextChoices = async () => {
|
|
54
|
+
const config = await loadUserConfig();
|
|
55
|
+
const contexts = normalizeContexts(config.contexts);
|
|
56
|
+
|
|
57
|
+
const choices = [{ title: 'core', value: 'core' }];
|
|
58
|
+
for (const context of contexts) {
|
|
59
|
+
choices.push({ title: context.name, value: context.name });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return choices;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const getCoreResponses = async () => {
|
|
66
|
+
const responsesPath = path.join(process.cwd(), 'src', 'core', 'response');
|
|
67
|
+
try {
|
|
68
|
+
const files = await fs.readdir(responsesPath);
|
|
69
|
+
return files.filter((file) => file.endsWith('.ts') && file !== 'index.ts').map((file) => file.replace(/\.ts$/, ''));
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const hasResponseInterface = async (responseName) => {
|
|
76
|
+
const responsePath = path.join(process.cwd(), 'src', 'core', 'response', `${responseName}.ts`);
|
|
77
|
+
try {
|
|
78
|
+
const content = await fs.readFile(responsePath, 'utf-8');
|
|
79
|
+
const interfaceRegex = new RegExp(`export\\s+interface\\s+I${responseName}\\b`);
|
|
80
|
+
return interfaceRegex.test(content);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getConfiguredCoreAliasOrThrow = async () => {
|
|
87
|
+
const configRef = resolveConfigFile();
|
|
88
|
+
if (!configRef.exists) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const content = await fs.readFile(configRef.filepath, 'utf-8');
|
|
95
|
+
const aliasMatch = content.match(/coreAlias\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
96
|
+
const alias = aliasMatch?.[1]?.trim();
|
|
97
|
+
|
|
98
|
+
if (!alias) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return alias.startsWith('$') ? alias : `$${alias}`;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const resolveTargetPath = (target) => {
|
|
108
|
+
if (target === 'core') {
|
|
109
|
+
return path.join(process.cwd(), 'src', 'core', 'datasource');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return path.join(process.cwd(), 'src', 'app', target, 'layers', 'infrastructure', 'http', 'datasources');
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export default async function createDatasource() {
|
|
116
|
+
const { datasourceNameRaw } = await prompts({
|
|
117
|
+
type: 'text',
|
|
118
|
+
name: 'datasourceNameRaw',
|
|
119
|
+
message: 'Datasource name:',
|
|
120
|
+
initial: 'MockApi'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const className = ensureDatasourceClassName(String(datasourceNameRaw ?? 'MockApi'));
|
|
124
|
+
|
|
125
|
+
const contextChoices = await getContextChoices();
|
|
126
|
+
const { target } = await prompts({
|
|
127
|
+
type: 'select',
|
|
128
|
+
name: 'target',
|
|
129
|
+
message: 'Where to create datasource?',
|
|
130
|
+
choices: contextChoices,
|
|
131
|
+
initial: 0
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const selectedTarget = String(target ?? 'core');
|
|
135
|
+
const coreResponses = await getCoreResponses();
|
|
136
|
+
|
|
137
|
+
let selectedResponse = null;
|
|
138
|
+
if (coreResponses.length > 0) {
|
|
139
|
+
const { responseName } = await prompts({
|
|
140
|
+
type: 'select',
|
|
141
|
+
name: 'responseName',
|
|
142
|
+
message: 'Select response for datasource:',
|
|
143
|
+
choices: [{ title: 'No response', value: null }, ...coreResponses.map((response) => ({ title: response, value: response }))],
|
|
144
|
+
initial: 0
|
|
145
|
+
});
|
|
146
|
+
selectedResponse = responseName ? String(responseName) : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let content;
|
|
150
|
+
if (!selectedResponse) {
|
|
151
|
+
content = createDatasourceTemplateWithoutResponse(className);
|
|
152
|
+
} else {
|
|
153
|
+
const coreAlias = await getConfiguredCoreAliasOrThrow();
|
|
154
|
+
const withInterface = await hasResponseInterface(selectedResponse);
|
|
155
|
+
content = withInterface
|
|
156
|
+
? createDatasourceTemplateWithResponseAndInterface(className, selectedResponse, coreAlias)
|
|
157
|
+
: createDatasourceTemplateWithResponse(className, selectedResponse, coreAlias);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const targetPath = resolveTargetPath(selectedTarget);
|
|
161
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
162
|
+
|
|
163
|
+
const filePath = path.join(targetPath, `${className}.ts`);
|
|
164
|
+
const created = await writeIfNotExists(filePath, content);
|
|
165
|
+
await updateIndexTs(targetPath);
|
|
166
|
+
|
|
167
|
+
if (!created) {
|
|
168
|
+
console.log(`⚠️ Datasource "${className}" already exists. File was not overwritten.`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(`✅ Datasource created: ${filePath}`);
|
|
173
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
|
|
5
|
+
import { toPascalCase } from '../utils/contextUtils.js';
|
|
6
|
+
import { loadUserConfig, resolveConfigFile } from '../utils/loadConfig.js';
|
|
7
|
+
import { normalizeContexts } from '../init/initAliases.js';
|
|
8
|
+
|
|
9
|
+
type ContextChoice = { title: string; value: string };
|
|
10
|
+
|
|
11
|
+
const createDatasourceTemplateWithoutResponse = (
|
|
12
|
+
className: string
|
|
13
|
+
): string => `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
14
|
+
|
|
15
|
+
export class ${className} extends BaseHttpDatasource {
|
|
16
|
+
\tasync createRequest<T>(callback: CreateRequestCallbackType<T>) {
|
|
17
|
+
\t\treturn await this.createRawRequest<T>(callback);
|
|
18
|
+
\t}
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const createDatasourceTemplateWithResponseAndInterface = (
|
|
23
|
+
className: string,
|
|
24
|
+
responseName: string,
|
|
25
|
+
coreAlias: string
|
|
26
|
+
): string => `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
27
|
+
import { ${responseName}, type I${responseName} } from '${coreAlias}/response';
|
|
28
|
+
|
|
29
|
+
export class ${className} extends BaseHttpDatasource {
|
|
30
|
+
\tasync createRequest<T>(callback: CreateRequestCallbackType<I${responseName}<T>>) {
|
|
31
|
+
\t\treturn new ${responseName}<T>(await this.createRawRequest<I${responseName}<T>>(callback));
|
|
32
|
+
\t}
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const createDatasourceTemplateWithResponse = (
|
|
37
|
+
className: string,
|
|
38
|
+
responseName: string,
|
|
39
|
+
coreAlias: string
|
|
40
|
+
): string => `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
41
|
+
import { ${responseName} } from '${coreAlias}/response';
|
|
42
|
+
|
|
43
|
+
export class ${className} extends BaseHttpDatasource {
|
|
44
|
+
\tasync createRequest<T>(callback: CreateRequestCallbackType<T>) {
|
|
45
|
+
\t\treturn new ${responseName}<T>(await this.createRawRequest<T>(callback));
|
|
46
|
+
\t}
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const ensureDatasourceClassName = (rawName: string): string => {
|
|
51
|
+
const normalized = toPascalCase(rawName || 'MockApi');
|
|
52
|
+
return normalized.endsWith('Datasource') ? normalized : `${normalized}Datasource`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getContextChoices = async (): Promise<ContextChoice[]> => {
|
|
56
|
+
const config = await loadUserConfig();
|
|
57
|
+
const contexts = normalizeContexts(config.contexts);
|
|
58
|
+
|
|
59
|
+
const choices: ContextChoice[] = [{ title: 'core', value: 'core' }];
|
|
60
|
+
for (const context of contexts) {
|
|
61
|
+
choices.push({ title: context.name, value: context.name });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return choices;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getCoreResponses = async (): Promise<string[]> => {
|
|
68
|
+
const responsesPath = path.join(process.cwd(), 'src', 'core', 'response');
|
|
69
|
+
try {
|
|
70
|
+
const files = await fs.readdir(responsesPath);
|
|
71
|
+
return files.filter((file) => file.endsWith('.ts') && file !== 'index.ts').map((file) => file.replace(/\.ts$/, ''));
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const hasResponseInterface = async (responseName: string): Promise<boolean> => {
|
|
78
|
+
const responsePath = path.join(process.cwd(), 'src', 'core', 'response', `${responseName}.ts`);
|
|
79
|
+
try {
|
|
80
|
+
const content = await fs.readFile(responsePath, 'utf-8');
|
|
81
|
+
const interfaceRegex = new RegExp(`export\\s+interface\\s+I${responseName}\\b`);
|
|
82
|
+
return interfaceRegex.test(content);
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getConfiguredCoreAliasOrThrow = async (): Promise<string> => {
|
|
89
|
+
const configRef = resolveConfigFile();
|
|
90
|
+
if (!configRef.exists) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const content = await fs.readFile(configRef.filepath, 'utf-8');
|
|
97
|
+
const aliasMatch = content.match(/coreAlias\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
98
|
+
const alias = aliasMatch?.[1]?.trim();
|
|
99
|
+
|
|
100
|
+
if (!alias) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return alias.startsWith('$') ? alias : `$${alias}`;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const resolveTargetPath = (target: string): string => {
|
|
110
|
+
if (target === 'core') {
|
|
111
|
+
return path.join(process.cwd(), 'src', 'core', 'datasource');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return path.join(process.cwd(), 'src', 'app', target, 'layers', 'infrastructure', 'http', 'datasources');
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export default async function createDatasource(): Promise<void> {
|
|
118
|
+
const { datasourceNameRaw } = await prompts({
|
|
119
|
+
type: 'text',
|
|
120
|
+
name: 'datasourceNameRaw',
|
|
121
|
+
message: 'Datasource name:',
|
|
122
|
+
initial: 'MockApi'
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const className = ensureDatasourceClassName(String(datasourceNameRaw ?? 'MockApi'));
|
|
126
|
+
|
|
127
|
+
const contextChoices = await getContextChoices();
|
|
128
|
+
const { target } = await prompts({
|
|
129
|
+
type: 'select',
|
|
130
|
+
name: 'target',
|
|
131
|
+
message: 'Where to create datasource?',
|
|
132
|
+
choices: contextChoices,
|
|
133
|
+
initial: 0
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const selectedTarget = String(target ?? 'core');
|
|
137
|
+
const coreResponses = await getCoreResponses();
|
|
138
|
+
|
|
139
|
+
let selectedResponse: string | null = null;
|
|
140
|
+
if (coreResponses.length > 0) {
|
|
141
|
+
const { responseName } = await prompts({
|
|
142
|
+
type: 'select',
|
|
143
|
+
name: 'responseName',
|
|
144
|
+
message: 'Select response for datasource:',
|
|
145
|
+
choices: [{ title: 'No response', value: null }, ...coreResponses.map((response) => ({ title: response, value: response }))],
|
|
146
|
+
initial: 0
|
|
147
|
+
});
|
|
148
|
+
selectedResponse = responseName ? String(responseName) : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let content: string;
|
|
152
|
+
if (!selectedResponse) {
|
|
153
|
+
content = createDatasourceTemplateWithoutResponse(className);
|
|
154
|
+
} else {
|
|
155
|
+
const coreAlias = await getConfiguredCoreAliasOrThrow();
|
|
156
|
+
const withInterface = await hasResponseInterface(selectedResponse);
|
|
157
|
+
content = withInterface
|
|
158
|
+
? createDatasourceTemplateWithResponseAndInterface(className, selectedResponse, coreAlias)
|
|
159
|
+
: createDatasourceTemplateWithResponse(className, selectedResponse, coreAlias);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const targetPath = resolveTargetPath(selectedTarget);
|
|
163
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
164
|
+
|
|
165
|
+
const filePath = path.join(targetPath, `${className}.ts`);
|
|
166
|
+
const created = await writeIfNotExists(filePath, content);
|
|
167
|
+
await updateIndexTs(targetPath);
|
|
168
|
+
|
|
169
|
+
if (!created) {
|
|
170
|
+
console.log(`⚠️ Datasource "${className}" already exists. File was not overwritten.`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(`✅ Datasource created: ${filePath}`);
|
|
175
|
+
}
|