@azure-net/cli 4.0.0-0 ā 4.0.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/azure-net.js +65 -34
- package/bin/azure-net.ts +65 -34
- package/eslint.config.js +15 -0
- package/package.json +1 -1
- package/src/checks/checkDomain.js +201 -0
- package/src/checks/checkDomain.ts +213 -0
- package/src/checks/checkFoldersStructure.js +70 -0
- package/src/checks/checkFoldersStructure.ts +85 -0
- package/src/checks/checkInternal.js +35 -0
- package/src/checks/checkInternal.ts +40 -0
- package/src/checks/checkLayerBoundaries.js +178 -0
- package/src/checks/checkLayerBoundaries.ts +189 -0
- package/src/checks/checkPresenterNames.js +202 -0
- package/src/checks/checkPresenterNames.ts +208 -0
- package/src/checks/checkProject.js +42 -0
- package/src/checks/checkProject.ts +45 -0
- package/src/checks/checkProviderNames.js +158 -0
- package/src/checks/checkProviderNames.ts +165 -0
- package/src/checks/index.js +7 -0
- package/src/checks/index.ts +7 -0
- package/src/generate/layers/generateRepository.ts +123 -0
- package/src/generate/layers/generateRepositoryMethod.ts +102 -0
- package/src/generate/layers/generateUseCases.ts +50 -0
- package/src/generate/layers/repositoryShared.ts +657 -0
- package/src/generate/module/generateModulePreset.js +307 -0
- package/src/generate/module/generateModulePreset.ts +337 -0
- package/src/generate/module/generateSchemaFromType.js +46 -8
- package/src/generate/module/generateSchemaFromType.ts +52 -8
- package/src/generate/module/repositoryModuleShared.ts +661 -0
- package/src/utils/contextUtils.ts +89 -0
- package/src/utils/fileUtils.ts +100 -0
- package/src/utils/loadConfig.ts +69 -0
- package/src/commands/generate/generateCrudBase.js +0 -188
- package/src/commands/generate/generateCrudPresenter.js +0 -225
- package/src/commands/generate/generateModule.js +0 -31
- package/src/commands/generate/generateModuleBase.js +0 -130
- package/src/commands/generate/generateModulePresenter.js +0 -103
- package/src/commands/generate/generatePresenter.js +0 -80
- package/src/commands/generate/generateRepository.js +0 -70
- package/src/commands/generate/generateSchema.js +0 -100
- package/src/commands/generate/generateService.js +0 -66
- package/src/commands/generate/index.js +0 -9
|
@@ -0,0 +1,89 @@
|
|
|
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 APP_PATH = path.join(process.cwd(), 'src/app');
|
|
7
|
+
const CORE_PATH = path.join(process.cwd(), 'src/core');
|
|
8
|
+
|
|
9
|
+
function normalizeContextNames(rawContexts: unknown): string[] {
|
|
10
|
+
if (!Array.isArray(rawContexts)) return [];
|
|
11
|
+
|
|
12
|
+
return rawContexts
|
|
13
|
+
.map((item) => {
|
|
14
|
+
if (typeof item === 'string') {
|
|
15
|
+
return item.trim();
|
|
16
|
+
}
|
|
17
|
+
if (!item || typeof item !== 'object') return '';
|
|
18
|
+
return String(item.name ?? '').trim();
|
|
19
|
+
})
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function selectContext(message = 'Select context:'): Promise<string | null> {
|
|
24
|
+
const config = await loadUserConfig();
|
|
25
|
+
const contexts = normalizeContextNames(config.contexts);
|
|
26
|
+
|
|
27
|
+
const { context } = await prompts({
|
|
28
|
+
type: 'select',
|
|
29
|
+
name: 'context',
|
|
30
|
+
message,
|
|
31
|
+
choices: [...contexts.map((c) => ({ title: c, value: c })), { title: 'core', value: 'core' }]
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return context ? String(context) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getContextPath(context: string): string {
|
|
38
|
+
return context === 'core' ? CORE_PATH : path.join(APP_PATH, context, 'layers');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function getAvailableFiles(dir: string, pattern = '.ts'): Promise<string[]> {
|
|
42
|
+
try {
|
|
43
|
+
const files = await fs.readdir(dir);
|
|
44
|
+
return files.filter((f) => f.endsWith(pattern) && f !== 'index.ts').map((f) => f.replace(pattern, ''));
|
|
45
|
+
} catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getApplicationProviderServices(context: string): Promise<string[]> {
|
|
51
|
+
const contextPath = getContextPath(context);
|
|
52
|
+
const providerPath = path.join(contextPath, 'application/providers/ApplicationProvider.ts');
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const content = await fs.readFile(providerPath, 'utf-8');
|
|
56
|
+
// Extract service names from ApplicationProvider
|
|
57
|
+
const serviceRegex = /(\w+Service):\s*\(/g;
|
|
58
|
+
const services: string[] = [];
|
|
59
|
+
let match;
|
|
60
|
+
|
|
61
|
+
while ((match = serviceRegex.exec(content)) !== null) {
|
|
62
|
+
const serviceName = match[1];
|
|
63
|
+
if (serviceName) services.push(serviceName);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return services;
|
|
67
|
+
} catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function toPascalCase(str: string): string {
|
|
73
|
+
return str
|
|
74
|
+
.split(/[-_\s]+/)
|
|
75
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
76
|
+
.join('');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function toCamelCase(str: string): string {
|
|
80
|
+
const pascal = toPascalCase(str);
|
|
81
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function toKebabCase(str: string): string {
|
|
85
|
+
return str
|
|
86
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
87
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
88
|
+
.toLowerCase();
|
|
89
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export async function writeIfNotExists(filepath: string, content: string): Promise<boolean> {
|
|
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: string, filePattern = '.ts', ignore: string[] = ['index.ts']): Promise<void> {
|
|
18
|
+
try {
|
|
19
|
+
const files = (await fs.readdir(dir)).filter((f) => f.endsWith(filePattern) && !ignore.includes(f));
|
|
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
|
+
} catch {
|
|
23
|
+
// Directory doesn't exist yet
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function updateCoreIndex(): Promise<void> {
|
|
28
|
+
const corePath = path.join(process.cwd(), 'src/core');
|
|
29
|
+
try {
|
|
30
|
+
const dirs = await fs.readdir(corePath, { withFileTypes: true });
|
|
31
|
+
const validDirs: string[] = [];
|
|
32
|
+
|
|
33
|
+
for (const dir of dirs) {
|
|
34
|
+
if (dir.isDirectory()) {
|
|
35
|
+
// Check if directory has any files
|
|
36
|
+
const dirPath = path.join(corePath, dir.name);
|
|
37
|
+
const files = await fs.readdir(dirPath);
|
|
38
|
+
if (files.length > 0) {
|
|
39
|
+
validDirs.push(dir.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (validDirs.length > 0) {
|
|
45
|
+
const content = validDirs.map((d) => `export * from './${d}';`).join('\n') + '\n';
|
|
46
|
+
await fs.writeFile(path.join(corePath, 'index.ts'), content, 'utf-8');
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Core directory doesn't exist
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function ensureIndexExports(layerPath: string): Promise<void> {
|
|
54
|
+
try {
|
|
55
|
+
const subdirs = await fs.readdir(layerPath, { withFileTypes: true });
|
|
56
|
+
const exportLines = subdirs.filter((d) => d.isDirectory()).map((d) => `export * from './${d.name}';`);
|
|
57
|
+
|
|
58
|
+
if (exportLines.length > 0) {
|
|
59
|
+
await fs.writeFile(path.join(layerPath, 'index.ts'), exportLines.join('\n') + '\n', 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Directory doesn't exist
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function injectIntoFile(
|
|
67
|
+
filepath: string,
|
|
68
|
+
searchPattern: string,
|
|
69
|
+
newContent: string,
|
|
70
|
+
position: 'after' | 'before' = 'after'
|
|
71
|
+
): Promise<boolean> {
|
|
72
|
+
try {
|
|
73
|
+
let content = await fs.readFile(filepath, 'utf-8');
|
|
74
|
+
const index = content.indexOf(searchPattern);
|
|
75
|
+
|
|
76
|
+
if (index === -1) {
|
|
77
|
+
console.warn(`Pattern not found in ${filepath}`);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (content.includes(newContent.trim())) {
|
|
82
|
+
console.log(`Content already exists in ${filepath}`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (position === 'after') {
|
|
87
|
+
const insertIndex = index + searchPattern.length;
|
|
88
|
+
content = content.slice(0, insertIndex) + '\n' + newContent + content.slice(insertIndex);
|
|
89
|
+
} else {
|
|
90
|
+
content = content.slice(0, index) + newContent + '\n' + content.slice(index);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await fs.writeFile(filepath, content, 'utf-8');
|
|
94
|
+
console.log(`ā
Updated: ${filepath}`);
|
|
95
|
+
return true;
|
|
96
|
+
} catch (error: unknown) {
|
|
97
|
+
console.error(`Failed to update ${filepath}:`, error instanceof Error ? error.message : String(error));
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export type AzureNetConfig = {
|
|
6
|
+
contexts: Array<string | { name?: string; alias?: string }>;
|
|
7
|
+
defaultContext: string;
|
|
8
|
+
coreAlias: string;
|
|
9
|
+
sharedAlias?: string;
|
|
10
|
+
packageManager?: string;
|
|
11
|
+
defaultSchemaFactory?: string;
|
|
12
|
+
defaultPresenterFactory?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CONFIG: AzureNetConfig = {
|
|
17
|
+
contexts: [],
|
|
18
|
+
defaultContext: 'web',
|
|
19
|
+
coreAlias: '$core'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function toObjectFromDefaultExport(content: string): Record<string, unknown> {
|
|
23
|
+
const source = String(content ?? '').trim();
|
|
24
|
+
if (!source.length) return {};
|
|
25
|
+
|
|
26
|
+
const replacedExport = source.replace(/^\s*export\s+default\s+/m, 'return ');
|
|
27
|
+
if (replacedExport === source) return {};
|
|
28
|
+
|
|
29
|
+
const withSemicolon = /;\s*$/.test(replacedExport) ? replacedExport : `${replacedExport};`;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return new Function(withSemicolon)() ?? {};
|
|
33
|
+
} catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveConfigFile(cwd = process.cwd()): { exists: boolean; filepath: string; type: 'ts' | 'js' } {
|
|
39
|
+
const tsPath = path.join(cwd, 'azure-net.config.ts');
|
|
40
|
+
const jsPath = path.join(cwd, 'azure-net.config.js');
|
|
41
|
+
|
|
42
|
+
if (fsSync.existsSync(tsPath)) {
|
|
43
|
+
return { exists: true, filepath: tsPath, type: 'ts' };
|
|
44
|
+
}
|
|
45
|
+
if (fsSync.existsSync(jsPath)) {
|
|
46
|
+
return { exists: true, filepath: jsPath, type: 'js' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { exists: false, filepath: tsPath, type: 'ts' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function loadUserConfig(): Promise<AzureNetConfig> {
|
|
53
|
+
const { exists, filepath } = resolveConfigFile();
|
|
54
|
+
if (!exists) {
|
|
55
|
+
return { ...DEFAULT_CONFIG };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const content = await fs.readFile(filepath, 'utf-8');
|
|
60
|
+
const parsed = toObjectFromDefaultExport(content);
|
|
61
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
62
|
+
return { ...DEFAULT_CONFIG };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { ...DEFAULT_CONFIG, ...(parsed as Record<string, unknown>) };
|
|
66
|
+
} catch {
|
|
67
|
+
return { ...DEFAULT_CONFIG };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import prompts from 'prompts';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { selectContext, getContextPath, toPascalCase, toCamelCase, getAvailableFiles, toKebabCase } from '../../utils/contextUtils.js';
|
|
4
|
-
import { writeIfNotExists, updateIndexTs, updateCoreIndex } from '../../utils/fileUtils.js';
|
|
5
|
-
|
|
6
|
-
// Templates for CRUD base generation
|
|
7
|
-
const entityTemplate = `export interface I{{name}} {
|
|
8
|
-
\tid: number;
|
|
9
|
-
\t// Add entity fields here
|
|
10
|
-
\tcreated_at: string;
|
|
11
|
-
\tupdated_at: string;
|
|
12
|
-
}`;
|
|
13
|
-
|
|
14
|
-
const portsIndexTemplate = `import type { I{{name}} } from '\${{context}}/domain/{{entityLower}}/model';
|
|
15
|
-
|
|
16
|
-
export type I{{name}}Collection = I{{name}}[];
|
|
17
|
-
|
|
18
|
-
export interface I{{name}}CollectionQuery {
|
|
19
|
-
\tpage?: number;
|
|
20
|
-
\tperPage?: number;
|
|
21
|
-
\t[key: string]: unknown;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface I{{name}}CreateRequest {
|
|
25
|
-
\t// Add request fields here
|
|
26
|
-
\t[key: string]: unknown;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface I{{name}}UpdateRequest {
|
|
30
|
-
\t// Add request fields here
|
|
31
|
-
\t[key: string]: unknown;
|
|
32
|
-
}`;
|
|
33
|
-
|
|
34
|
-
const repositoryTemplate = `import { {{datasource}} } from '{{datasourceImport}}';
|
|
35
|
-
import type {
|
|
36
|
-
\tI{{name}},
|
|
37
|
-
\tI{{name}}Collection,
|
|
38
|
-
\tI{{name}}CollectionQuery,
|
|
39
|
-
\tI{{name}}CreateRequest,
|
|
40
|
-
\tI{{name}}UpdateRequest
|
|
41
|
-
} from '\${{context}}/domain/{{entityLower}}';
|
|
42
|
-
|
|
43
|
-
export class {{name}}Repository {
|
|
44
|
-
\tprivate endpoint = '{{endpoint}}';
|
|
45
|
-
\t
|
|
46
|
-
\tconstructor(private {{datasourceVar}}: {{datasource}}) {}
|
|
47
|
-
\t
|
|
48
|
-
\tpublic async collection(query?: I{{name}}CollectionQuery) {
|
|
49
|
-
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}Collection>(({ http, query: q }) =>
|
|
50
|
-
\t\t\thttp.get(\`\${this.endpoint}\${q.build(query ?? {})}\`)
|
|
51
|
-
\t\t).then(res => res.getData());
|
|
52
|
-
\t}
|
|
53
|
-
\t
|
|
54
|
-
\tpublic async resource(id: number) {
|
|
55
|
-
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
|
|
56
|
-
\t\t\thttp.get(\`\${this.endpoint}/\${id}\`)
|
|
57
|
-
\t\t).then(res => res.getData());
|
|
58
|
-
\t}
|
|
59
|
-
\t
|
|
60
|
-
\tpublic async create(data: I{{name}}CreateRequest) {
|
|
61
|
-
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
|
|
62
|
-
\t\t\thttp.post(this.endpoint, { json: data })
|
|
63
|
-
\t\t).then(res => res.getData());
|
|
64
|
-
\t}
|
|
65
|
-
\t
|
|
66
|
-
\tpublic async update(id: number, data: I{{name}}UpdateRequest) {
|
|
67
|
-
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
|
|
68
|
-
\t\t\thttp.put(\`\${this.endpoint}/\${id}\`, { json: data })
|
|
69
|
-
\t\t).then(res => res.getData());
|
|
70
|
-
\t}
|
|
71
|
-
\t
|
|
72
|
-
\tpublic async destroy(id: number) {
|
|
73
|
-
\t\treturn this.{{datasourceVar}}.createRequest<void>(({ http }) =>
|
|
74
|
-
\t\t\thttp.delete(\`\${this.endpoint}/\${id}\`)
|
|
75
|
-
\t\t).then(() => undefined);
|
|
76
|
-
\t}
|
|
77
|
-
}`;
|
|
78
|
-
|
|
79
|
-
const serviceTemplate = `import { ClassMirror } from '@azure-net/kit';
|
|
80
|
-
import { {{name}}Repository } from '\${{context}}/infrastructure/http/repositories';
|
|
81
|
-
|
|
82
|
-
export class {{name}}Service extends ClassMirror<{{name}}Repository> {
|
|
83
|
-
\tconstructor(private {{camelName}}Repository: {{name}}Repository) {
|
|
84
|
-
\t\tsuper({{camelName}}Repository);
|
|
85
|
-
\t}
|
|
86
|
-
\t
|
|
87
|
-
\tdeclare collection: {{name}}Repository['collection'];
|
|
88
|
-
\tdeclare resource: {{name}}Repository['resource'];
|
|
89
|
-
\tdeclare create: {{name}}Repository['create'];
|
|
90
|
-
\tdeclare update: {{name}}Repository['update'];
|
|
91
|
-
\tdeclare destroy: {{name}}Repository['destroy'];
|
|
92
|
-
}`;
|
|
93
|
-
|
|
94
|
-
export default async function generateCrudBase() {
|
|
95
|
-
const context = await selectContext('Select context for CRUD base:');
|
|
96
|
-
|
|
97
|
-
const { name } = await prompts({
|
|
98
|
-
type: 'text',
|
|
99
|
-
name: 'name',
|
|
100
|
-
message: 'Entity name (PascalCase):'
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const { endpoint } = await prompts({
|
|
104
|
-
type: 'text',
|
|
105
|
-
name: 'endpoint',
|
|
106
|
-
message: 'API endpoint (e.g. /api/users):'
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Get available datasources
|
|
110
|
-
const contextPath = getContextPath(context);
|
|
111
|
-
const coreDatasources = await getAvailableFiles(path.join(process.cwd(), 'src/core/datasources'));
|
|
112
|
-
const contextDatasources = await getAvailableFiles(path.join(contextPath, 'infrastructure/http/datasources'));
|
|
113
|
-
|
|
114
|
-
const allDatasources = [
|
|
115
|
-
...coreDatasources.map((d) => ({ title: `${d} (core)`, value: { name: d, from: 'core' } })),
|
|
116
|
-
...contextDatasources.map((d) => ({ title: `${d} (${context})`, value: { name: d, from: context } }))
|
|
117
|
-
];
|
|
118
|
-
|
|
119
|
-
const { datasource } = await prompts({
|
|
120
|
-
type: 'select',
|
|
121
|
-
name: 'datasource',
|
|
122
|
-
message: 'Select datasource:',
|
|
123
|
-
choices: allDatasources
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const pascalName = toPascalCase(name);
|
|
127
|
-
const camelName = toCamelCase(name);
|
|
128
|
-
const entityLower = toKebabCase(pascalName);
|
|
129
|
-
|
|
130
|
-
console.log('\nš Generating CRUD base (repository & service)...\n');
|
|
131
|
-
|
|
132
|
-
// 1. Create Domain structure
|
|
133
|
-
const domainPath = path.join(contextPath, 'domain', entityLower);
|
|
134
|
-
const modelPath = path.join(domainPath, 'model');
|
|
135
|
-
const portsPath = path.join(domainPath, 'ports');
|
|
136
|
-
|
|
137
|
-
// Create Model
|
|
138
|
-
await writeIfNotExists(path.join(modelPath, 'index.ts'), entityTemplate.replace(/{{name}}/g, pascalName));
|
|
139
|
-
|
|
140
|
-
// Create Ports
|
|
141
|
-
await writeIfNotExists(
|
|
142
|
-
path.join(portsPath, 'index.ts'),
|
|
143
|
-
portsIndexTemplate
|
|
144
|
-
.replace(/{{name}}/g, pascalName)
|
|
145
|
-
.replace(/{{context}}/g, context)
|
|
146
|
-
.replace(/{{entityLower}}/g, entityLower)
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
// Create Domain module index
|
|
150
|
-
await writeIfNotExists(path.join(domainPath, 'index.ts'), `export * from './model';\nexport * from './ports';`);
|
|
151
|
-
|
|
152
|
-
// 2. Create Repository
|
|
153
|
-
const repoPath = path.join(contextPath, 'infrastructure/http/repositories');
|
|
154
|
-
const datasourceImport = datasource.from === 'core' ? '$core/datasources' : `\$${context}/infrastructure/http/datasources`;
|
|
155
|
-
|
|
156
|
-
await writeIfNotExists(
|
|
157
|
-
path.join(repoPath, `${pascalName}Repository.ts`),
|
|
158
|
-
repositoryTemplate
|
|
159
|
-
.replace(/{{name}}/g, pascalName)
|
|
160
|
-
.replace(/{{context}}/g, context)
|
|
161
|
-
.replace(/{{endpoint}}/g, endpoint)
|
|
162
|
-
.replace(/{{datasource}}/g, datasource.name)
|
|
163
|
-
.replace(/{{datasourceImport}}/g, datasourceImport)
|
|
164
|
-
.replace(/{{datasourceVar}}/g, toCamelCase(datasource.name))
|
|
165
|
-
.replace(/{{entityLower}}/g, entityLower)
|
|
166
|
-
);
|
|
167
|
-
await updateIndexTs(repoPath);
|
|
168
|
-
|
|
169
|
-
// 3. Create Service
|
|
170
|
-
const servicePath = path.join(contextPath, 'application/services');
|
|
171
|
-
await writeIfNotExists(
|
|
172
|
-
path.join(servicePath, `${pascalName}Service.ts`),
|
|
173
|
-
serviceTemplate
|
|
174
|
-
.replace(/{{name}}/g, pascalName)
|
|
175
|
-
.replace(/{{camelName}}/g, camelName)
|
|
176
|
-
.replace(/{{context}}/g, context)
|
|
177
|
-
);
|
|
178
|
-
await updateIndexTs(servicePath);
|
|
179
|
-
|
|
180
|
-
// Update core index
|
|
181
|
-
await updateCoreIndex();
|
|
182
|
-
|
|
183
|
-
console.log(`ā
CRUD base for ${pascalName} created successfully!`);
|
|
184
|
-
console.log(`\nš” Remember to:`);
|
|
185
|
-
console.log(` 1. Add ${pascalName}Repository to InfrastructureProvider`);
|
|
186
|
-
console.log(` 2. Add ${pascalName}Service to ApplicationProvider`);
|
|
187
|
-
console.log(` 3. Use 'make:crud-presenter' to create the CRUD presenter`);
|
|
188
|
-
}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import prompts from 'prompts';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
selectContext,
|
|
5
|
-
getContextPath,
|
|
6
|
-
toPascalCase,
|
|
7
|
-
getAvailableFiles,
|
|
8
|
-
getApplicationProviderServices,
|
|
9
|
-
toKebabCase
|
|
10
|
-
} from '../../utils/contextUtils.js';
|
|
11
|
-
import { updateIndexTs, writeIfNotExists, updateCoreIndex } from '../../utils/fileUtils.js';
|
|
12
|
-
|
|
13
|
-
const createSchemaTemplate = `import { {{schemaFactory}} } from '{{schemaImport}}';
|
|
14
|
-
import type { I{{name}}CreateRequest } from '\${{context}}/domain/{{entityLower}}';
|
|
15
|
-
|
|
16
|
-
export const Create{{name}}Schema = {{schemaFactory}}<I{{name}}CreateRequest>()
|
|
17
|
-
\t.rules(() => ({
|
|
18
|
-
\t\t// Add validation rules here
|
|
19
|
-
\t}))
|
|
20
|
-
\t.create();`;
|
|
21
|
-
|
|
22
|
-
const updateSchemaTemplate = `import { {{schemaFactory}} } from '{{schemaImport}}';
|
|
23
|
-
import type { I{{name}}UpdateRequest } from '\${{context}}/domain/{{entityLower}}';
|
|
24
|
-
|
|
25
|
-
export const Update{{name}}Schema = {{schemaFactory}}<I{{name}}UpdateRequest>()
|
|
26
|
-
\t.rules(() => ({
|
|
27
|
-
\t\t// Add validation rules here
|
|
28
|
-
\t}))
|
|
29
|
-
\t.create();`;
|
|
30
|
-
|
|
31
|
-
const presenterWithCoreTemplate = `import { {{presenterFactory}} } from '$core/presenters';
|
|
32
|
-
import { ApplicationProvider } from '\${{context}}/application';
|
|
33
|
-
import { Create{{name}}Schema, Update{{name}}Schema } from './schema';
|
|
34
|
-
import type {
|
|
35
|
-
\tI{{name}},
|
|
36
|
-
\tI{{name}}CollectionQuery,
|
|
37
|
-
\tI{{name}}CreateRequest,
|
|
38
|
-
\tI{{name}}UpdateRequest
|
|
39
|
-
} from '\${{context}}/domain/{{entityLower}}';
|
|
40
|
-
|
|
41
|
-
export const {{name}}Presenter = {{presenterFactory}}('{{name}}Presenter', ({ createAsyncResource, createAsyncAction }) => {
|
|
42
|
-
\tconst { {{serviceName}} } = ApplicationProvider();
|
|
43
|
-
\t
|
|
44
|
-
\tconst collection = async (query?: I{{name}}CollectionQuery) =>
|
|
45
|
-
\t\tawait createAsyncResource({{serviceName}}.collection(query));
|
|
46
|
-
\t
|
|
47
|
-
\tconst resource = async (id: number) =>
|
|
48
|
-
\t\tawait createAsyncResource({{serviceName}}.resource(id));
|
|
49
|
-
\t
|
|
50
|
-
\tconst create = async (request: I{{name}}CreateRequest) =>
|
|
51
|
-
\t\tawait createAsyncAction<I{{name}}, I{{name}}CreateRequest>(() => {{serviceName}}.create(Create{{name}}Schema.from(request).json()));
|
|
52
|
-
\t
|
|
53
|
-
\tconst update = async (id: number, request: I{{name}}UpdateRequest) =>
|
|
54
|
-
\t\tawait createAsyncAction<I{{name}}, I{{name}}UpdateRequest>(() => {{serviceName}}.update(id, Update{{name}}Schema.from(request).json()));
|
|
55
|
-
\t
|
|
56
|
-
\tconst destroy = async (id: number) =>
|
|
57
|
-
\t\tawait createAsyncAction(() => {{serviceName}}.destroy(id));
|
|
58
|
-
\t
|
|
59
|
-
\treturn { collection, resource, create, update, destroy };
|
|
60
|
-
});`;
|
|
61
|
-
|
|
62
|
-
const presenterWithoutCoreTemplate = `import { createPresenter } from '@azure-net/kit';
|
|
63
|
-
import { ApplicationProvider } from '\${{context}}/application';
|
|
64
|
-
import { Create{{name}}Schema, Update{{name}}Schema } from './schema';
|
|
65
|
-
import type {
|
|
66
|
-
\tI{{name}}CollectionQuery,
|
|
67
|
-
\tI{{name}}CreateRequest,
|
|
68
|
-
\tI{{name}}UpdateRequest
|
|
69
|
-
} from '\${{context}}/domain/{{entityLower}}';
|
|
70
|
-
|
|
71
|
-
export const {{name}}Presenter = createPresenter('{{name}}Presenter', () => {
|
|
72
|
-
\tconst { {{serviceName}} } = ApplicationProvider();
|
|
73
|
-
\t
|
|
74
|
-
\tconst collection = async (query?: I{{name}}CollectionQuery) =>
|
|
75
|
-
\t\tawait {{serviceName}}.collection(query);
|
|
76
|
-
\t
|
|
77
|
-
\tconst resource = async (id: number) =>
|
|
78
|
-
\t\tawait {{serviceName}}.resource(id);
|
|
79
|
-
\t
|
|
80
|
-
\tconst create = async (request: I{{name}}CreateRequest) =>
|
|
81
|
-
\t\tawait {{serviceName}}.create(Create{{name}}Schema.from(request).json());
|
|
82
|
-
\t
|
|
83
|
-
\tconst update = async (id: number, request: I{{name}}UpdateRequest) =>
|
|
84
|
-
\t\tawait {{serviceName}}.update(id, Update{{name}}Schema.from(request).json()));
|
|
85
|
-
\t
|
|
86
|
-
\tconst destroy = async (id: number) =>
|
|
87
|
-
\t\tawait {{serviceName}}.destroy(id);
|
|
88
|
-
\t
|
|
89
|
-
\treturn { collection, resource, create, update, destroy };
|
|
90
|
-
});`;
|
|
91
|
-
|
|
92
|
-
export default async function generateCrudPresenter() {
|
|
93
|
-
const context = await selectContext('Select context for CRUD presenter:');
|
|
94
|
-
|
|
95
|
-
// Get available services from ApplicationProvider
|
|
96
|
-
const services = await getApplicationProviderServices(context);
|
|
97
|
-
|
|
98
|
-
if (services.length === 0) {
|
|
99
|
-
console.error('ā No services found in ApplicationProvider. Add services manually first.');
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const { service } = await prompts({
|
|
104
|
-
type: 'select',
|
|
105
|
-
name: 'service',
|
|
106
|
-
message: 'Select service for CRUD:',
|
|
107
|
-
choices: services.map((s) => ({ title: s, value: s }))
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Extract entity name from service name
|
|
111
|
-
const entityName = service.replace('Service', '');
|
|
112
|
-
const pascalName = toPascalCase(entityName);
|
|
113
|
-
const entityLower = toKebabCase(pascalName);
|
|
114
|
-
|
|
115
|
-
// Get schema factory
|
|
116
|
-
const coreSchemas = await getAvailableFiles(path.join(process.cwd(), 'src/core/schemas'));
|
|
117
|
-
|
|
118
|
-
const { schemaType } = await prompts({
|
|
119
|
-
type: 'select',
|
|
120
|
-
name: 'schemaType',
|
|
121
|
-
message: 'Select schema factory:',
|
|
122
|
-
choices: [
|
|
123
|
-
{ title: 'Use createSchemaFactory from package', value: 'package' },
|
|
124
|
-
...coreSchemas.map((s) => ({ title: `Use ${s} from core`, value: s }))
|
|
125
|
-
]
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Get presenter factory
|
|
129
|
-
const corePresenters = await getAvailableFiles(path.join(process.cwd(), 'src/core/presenters'));
|
|
130
|
-
|
|
131
|
-
const { presenterType } = await prompts({
|
|
132
|
-
type: 'select',
|
|
133
|
-
name: 'presenterType',
|
|
134
|
-
message: 'Select presenter factory:',
|
|
135
|
-
choices: [
|
|
136
|
-
{ title: 'Use createPresenter from package', value: 'package' },
|
|
137
|
-
...corePresenters.map((p) => ({ title: `Use ${p} from core`, value: p }))
|
|
138
|
-
]
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const contextPath = getContextPath(context);
|
|
142
|
-
const deliveryModulePath = path.join(contextPath, 'delivery', entityLower);
|
|
143
|
-
const schemaPath = path.join(deliveryModulePath, 'schema');
|
|
144
|
-
|
|
145
|
-
console.log('\nš Generating CRUD presenter...\n');
|
|
146
|
-
|
|
147
|
-
// Create schemas
|
|
148
|
-
const schemaFactory = schemaType === 'package' ? 'Schema' : schemaType;
|
|
149
|
-
const schemaImport = schemaType === 'package' ? '@azure-net/kit' : '$core/schemas';
|
|
150
|
-
|
|
151
|
-
if (schemaType === 'package') {
|
|
152
|
-
const schemaImportFull = `import { createSchemaFactory } from '@azure-net/kit';
|
|
153
|
-
import { createRules, validationMessagesI18n } from '@azure-net/kit/schema';
|
|
154
|
-
|
|
155
|
-
const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
|
|
156
|
-
|
|
157
|
-
await writeIfNotExists(
|
|
158
|
-
path.join(schemaPath, `Create${pascalName}Schema.ts`),
|
|
159
|
-
schemaImportFull +
|
|
160
|
-
'\n' +
|
|
161
|
-
createSchemaTemplate
|
|
162
|
-
.replace("import { {{schemaFactory}} } from '{{schemaImport}}';\n", '')
|
|
163
|
-
.replace(/{{name}}/g, pascalName)
|
|
164
|
-
.replace(/{{context}}/g, context)
|
|
165
|
-
.replace(/{{entityLower}}/g, entityLower)
|
|
166
|
-
.replace(/{{schemaFactory}}/g, 'Schema')
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
await writeIfNotExists(
|
|
170
|
-
path.join(schemaPath, `Update${pascalName}Schema.ts`),
|
|
171
|
-
schemaImportFull +
|
|
172
|
-
'\n' +
|
|
173
|
-
updateSchemaTemplate
|
|
174
|
-
.replace("import { {{schemaFactory}} } from '{{schemaImport}}';\n", '')
|
|
175
|
-
.replace(/{{name}}/g, pascalName)
|
|
176
|
-
.replace(/{{context}}/g, context)
|
|
177
|
-
.replace(/{{entityLower}}/g, entityLower)
|
|
178
|
-
.replace(/{{schemaFactory}}/g, 'Schema')
|
|
179
|
-
);
|
|
180
|
-
} else {
|
|
181
|
-
await writeIfNotExists(
|
|
182
|
-
path.join(schemaPath, `Create${pascalName}Schema.ts`),
|
|
183
|
-
createSchemaTemplate
|
|
184
|
-
.replace(/{{name}}/g, pascalName)
|
|
185
|
-
.replace(/{{context}}/g, context)
|
|
186
|
-
.replace(/{{entityLower}}/g, entityLower)
|
|
187
|
-
.replace(/{{schemaFactory}}/g, schemaFactory)
|
|
188
|
-
.replace(/{{schemaImport}}/g, schemaImport)
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
await writeIfNotExists(
|
|
192
|
-
path.join(schemaPath, `Update${pascalName}Schema.ts`),
|
|
193
|
-
updateSchemaTemplate
|
|
194
|
-
.replace(/{{name}}/g, pascalName)
|
|
195
|
-
.replace(/{{context}}/g, context)
|
|
196
|
-
.replace(/{{entityLower}}/g, entityLower)
|
|
197
|
-
.replace(/{{schemaFactory}}/g, schemaFactory)
|
|
198
|
-
.replace(/{{schemaImport}}/g, schemaImport)
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
await updateIndexTs(schemaPath);
|
|
202
|
-
|
|
203
|
-
// Create Presenter
|
|
204
|
-
const presenterTemplate = presenterType === 'package' ? presenterWithoutCoreTemplate : presenterWithCoreTemplate;
|
|
205
|
-
|
|
206
|
-
const presenterFactory = presenterType === 'package' ? 'createPresenter' : presenterType;
|
|
207
|
-
|
|
208
|
-
await writeIfNotExists(
|
|
209
|
-
path.join(deliveryModulePath, `${pascalName}Presenter.ts`),
|
|
210
|
-
presenterTemplate
|
|
211
|
-
.replace(/{{name}}/g, pascalName)
|
|
212
|
-
.replace(/{{context}}/g, context)
|
|
213
|
-
.replace(/{{serviceName}}/g, service)
|
|
214
|
-
.replace(/{{presenterFactory}}/g, presenterFactory)
|
|
215
|
-
.replace(/{{entityLower}}/g, entityLower)
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
// Create module index
|
|
219
|
-
await writeIfNotExists(path.join(deliveryModulePath, 'index.ts'), `export * from './${pascalName}Presenter';\nexport * from './schema';`);
|
|
220
|
-
|
|
221
|
-
// Update core index
|
|
222
|
-
await updateCoreIndex();
|
|
223
|
-
|
|
224
|
-
console.log(`ā
CRUD presenter for ${pascalName} created successfully!`);
|
|
225
|
-
}
|