@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.
Files changed (42) hide show
  1. package/bin/azure-net.js +65 -34
  2. package/bin/azure-net.ts +65 -34
  3. package/eslint.config.js +15 -0
  4. package/package.json +1 -1
  5. package/src/checks/checkDomain.js +201 -0
  6. package/src/checks/checkDomain.ts +213 -0
  7. package/src/checks/checkFoldersStructure.js +70 -0
  8. package/src/checks/checkFoldersStructure.ts +85 -0
  9. package/src/checks/checkInternal.js +35 -0
  10. package/src/checks/checkInternal.ts +40 -0
  11. package/src/checks/checkLayerBoundaries.js +178 -0
  12. package/src/checks/checkLayerBoundaries.ts +189 -0
  13. package/src/checks/checkPresenterNames.js +202 -0
  14. package/src/checks/checkPresenterNames.ts +208 -0
  15. package/src/checks/checkProject.js +42 -0
  16. package/src/checks/checkProject.ts +45 -0
  17. package/src/checks/checkProviderNames.js +158 -0
  18. package/src/checks/checkProviderNames.ts +165 -0
  19. package/src/checks/index.js +7 -0
  20. package/src/checks/index.ts +7 -0
  21. package/src/generate/layers/generateRepository.ts +123 -0
  22. package/src/generate/layers/generateRepositoryMethod.ts +102 -0
  23. package/src/generate/layers/generateUseCases.ts +50 -0
  24. package/src/generate/layers/repositoryShared.ts +657 -0
  25. package/src/generate/module/generateModulePreset.js +307 -0
  26. package/src/generate/module/generateModulePreset.ts +337 -0
  27. package/src/generate/module/generateSchemaFromType.js +46 -8
  28. package/src/generate/module/generateSchemaFromType.ts +52 -8
  29. package/src/generate/module/repositoryModuleShared.ts +661 -0
  30. package/src/utils/contextUtils.ts +89 -0
  31. package/src/utils/fileUtils.ts +100 -0
  32. package/src/utils/loadConfig.ts +69 -0
  33. package/src/commands/generate/generateCrudBase.js +0 -188
  34. package/src/commands/generate/generateCrudPresenter.js +0 -225
  35. package/src/commands/generate/generateModule.js +0 -31
  36. package/src/commands/generate/generateModuleBase.js +0 -130
  37. package/src/commands/generate/generateModulePresenter.js +0 -103
  38. package/src/commands/generate/generatePresenter.js +0 -80
  39. package/src/commands/generate/generateRepository.js +0 -70
  40. package/src/commands/generate/generateSchema.js +0 -100
  41. package/src/commands/generate/generateService.js +0 -66
  42. 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
- }