@azure-net/cli 2.0.4 ā 2.0.6
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 +23 -5
- package/package.json +1 -1
- package/src/commands/generate/generateCrudBase.js +188 -0
- package/src/commands/generate/generateCrudPresenter.js +220 -0
- package/src/commands/generate/generateDatasource.js +18 -89
- package/src/commands/generate/generateModule.js +9 -1
- package/src/commands/generate/generatePresenter.js +19 -49
- package/src/commands/generate/generatePresenterByService.js +102 -0
- package/src/commands/generate/generateRepository.js +9 -38
- package/src/commands/generate/generateResponse.js +6 -3
- package/src/commands/generate/generateService.js +3 -26
- package/src/commands/generate/generateServiceByRepo.js +86 -0
- package/src/commands/generate/index.js +4 -1
- package/src/commands/init/initAliases.js +1 -1
- package/src/commands/init/initMiddleware.js +4 -4
- package/src/commands/init/initSchema.js +2 -2
- package/src/commands/init/initStructure.js +76 -15
- package/src/commands/init/initTranslations.js +4 -4
- package/src/utils/contextUtils.js +25 -4
- package/src/commands/generate/generateCrud.js +0 -413
- package/src/utils/providerUtils.js +0 -112
package/bin/azure-net.js
CHANGED
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
generateSchema,
|
|
19
19
|
generatePresenter,
|
|
20
20
|
generateModule,
|
|
21
|
-
|
|
21
|
+
generateCrudBase,
|
|
22
|
+
generateCrudPresenter,
|
|
23
|
+
generatePresenterByService,
|
|
24
|
+
generateServiceByRepo
|
|
22
25
|
} from '../src/commands/generate/index.js';
|
|
23
26
|
|
|
24
27
|
const program = new Command();
|
|
@@ -32,7 +35,7 @@ program
|
|
|
32
35
|
program
|
|
33
36
|
.command('init:lint')
|
|
34
37
|
.description('Setup linting and commit tools')
|
|
35
|
-
.action(initLint)
|
|
38
|
+
.action(initLint);
|
|
36
39
|
|
|
37
40
|
program
|
|
38
41
|
.command('init:structure')
|
|
@@ -113,8 +116,23 @@ program
|
|
|
113
116
|
.action(generateModule);
|
|
114
117
|
|
|
115
118
|
program
|
|
116
|
-
.command('make:crud')
|
|
117
|
-
.description('Generate CRUD
|
|
118
|
-
.action(
|
|
119
|
+
.command('make:crud-base')
|
|
120
|
+
.description('Generate CRUD repository and service')
|
|
121
|
+
.action(generateCrudBase);
|
|
122
|
+
|
|
123
|
+
program
|
|
124
|
+
.command('make:crud-presenter')
|
|
125
|
+
.description('Generate CRUD presenter for existing service')
|
|
126
|
+
.action(generateCrudPresenter);
|
|
127
|
+
|
|
128
|
+
program
|
|
129
|
+
.command('make:presenter-by-service')
|
|
130
|
+
.description('Generate presenter from existing service')
|
|
131
|
+
.action(generatePresenterByService);
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.command('make:service-by-repo')
|
|
135
|
+
.description('Generate service from existing repository')
|
|
136
|
+
.action(generateServiceByRepo);
|
|
119
137
|
|
|
120
138
|
program.parse();
|
package/package.json
CHANGED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { selectContext, getContextPath, toPascalCase, toCamelCase, getAvailableFiles } from '../../utils/contextUtils.js';
|
|
4
|
+
import { writeIfNotExists, updateIndexTs } 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/Entities/{{name}}';
|
|
15
|
+
|
|
16
|
+
export interface I{{name}}Collection {
|
|
17
|
+
\tdata: I{{name}}[];
|
|
18
|
+
\ttotal: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface I{{name}}CollectionQuery {
|
|
22
|
+
\tpage?: number;
|
|
23
|
+
\tperPage?: number;
|
|
24
|
+
\t[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface I{{name}}CreateRequest {
|
|
28
|
+
\t// Add request fields here
|
|
29
|
+
\t[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface I{{name}}UpdateRequest {
|
|
33
|
+
\t// Add request fields here
|
|
34
|
+
\t[key: string]: unknown;
|
|
35
|
+
}`;
|
|
36
|
+
|
|
37
|
+
const repositoryTemplate = `import { {{datasource}} } from '{{datasourceImport}}';
|
|
38
|
+
import type { I{{name}} } from '\${{context}}/Domain/Entities/{{name}}';
|
|
39
|
+
import type {
|
|
40
|
+
\tI{{name}}Collection,
|
|
41
|
+
\tI{{name}}CollectionQuery,
|
|
42
|
+
\tI{{name}}CreateRequest,
|
|
43
|
+
\tI{{name}}UpdateRequest
|
|
44
|
+
} from '\${{context}}/Domain/Ports/{{name}}';
|
|
45
|
+
|
|
46
|
+
export class {{name}}Repository {
|
|
47
|
+
\tprivate endpoint = '{{endpoint}}';
|
|
48
|
+
\t
|
|
49
|
+
\tconstructor(private {{datasourceVar}}: {{datasource}}) {}
|
|
50
|
+
\t
|
|
51
|
+
\tpublic async collection(query?: I{{name}}CollectionQuery) {
|
|
52
|
+
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}Collection>(({ http, query: q }) =>
|
|
53
|
+
\t\t\thttp.get(\`\${this.endpoint}\${q.build(query || {})}\`)
|
|
54
|
+
\t\t).then(res => res.getData());
|
|
55
|
+
\t}
|
|
56
|
+
\t
|
|
57
|
+
\tpublic async resource(id: number) {
|
|
58
|
+
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
|
|
59
|
+
\t\t\thttp.get(\`\${this.endpoint}/\${id}\`)
|
|
60
|
+
\t\t).then(res => res.getData());
|
|
61
|
+
\t}
|
|
62
|
+
\t
|
|
63
|
+
\tpublic async create(data: I{{name}}CreateRequest) {
|
|
64
|
+
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
|
|
65
|
+
\t\t\thttp.post(this.endpoint, { json: data })
|
|
66
|
+
\t\t).then(res => res.getData());
|
|
67
|
+
\t}
|
|
68
|
+
\t
|
|
69
|
+
\tpublic async update(id: number, data: I{{name}}UpdateRequest) {
|
|
70
|
+
\t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
|
|
71
|
+
\t\t\thttp.put(\`\${this.endpoint}/\${id}\`, { json: data })
|
|
72
|
+
\t\t).then(res => res.getData());
|
|
73
|
+
\t}
|
|
74
|
+
\t
|
|
75
|
+
\tpublic async remove(id: number) {
|
|
76
|
+
\t\treturn this.{{datasourceVar}}.createRequest<void>(({ http }) =>
|
|
77
|
+
\t\t\thttp.delete(\`\${this.endpoint}/\${id}\`)
|
|
78
|
+
\t\t).then(() => undefined);
|
|
79
|
+
\t}
|
|
80
|
+
}`;
|
|
81
|
+
|
|
82
|
+
const serviceTemplate = `import { ClassMirror } from '@azure-net/kit';
|
|
83
|
+
import { {{name}}Repository } from '\${{context}}/Infrastructure/Http/Repositories';
|
|
84
|
+
|
|
85
|
+
export class {{name}}Service extends ClassMirror<{{name}}Repository> {
|
|
86
|
+
\tconstructor(private {{camelName}}Repository: {{name}}Repository) {
|
|
87
|
+
\t\tsuper({{camelName}}Repository);
|
|
88
|
+
\t}
|
|
89
|
+
\t
|
|
90
|
+
\tdeclare collection: {{name}}Repository['collection'];
|
|
91
|
+
\tdeclare resource: {{name}}Repository['resource'];
|
|
92
|
+
\tdeclare create: {{name}}Repository['create'];
|
|
93
|
+
\tdeclare update: {{name}}Repository['update'];
|
|
94
|
+
\tdeclare remove: {{name}}Repository['remove'];
|
|
95
|
+
}`;
|
|
96
|
+
|
|
97
|
+
export default async function generateCrudBase() {
|
|
98
|
+
const context = await selectContext('Select context for CRUD base:');
|
|
99
|
+
|
|
100
|
+
const { name } = await prompts({
|
|
101
|
+
type: 'text',
|
|
102
|
+
name: 'name',
|
|
103
|
+
message: 'Entity name (PascalCase):'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const { endpoint } = await prompts({
|
|
107
|
+
type: 'text',
|
|
108
|
+
name: 'endpoint',
|
|
109
|
+
message: 'API endpoint (e.g. /api/users):'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Get available datasources
|
|
113
|
+
const contextPath = getContextPath(context);
|
|
114
|
+
const sharedDatasources = await getAvailableFiles(
|
|
115
|
+
path.join(process.cwd(), 'src/app/shared/Datasource')
|
|
116
|
+
);
|
|
117
|
+
const contextDatasources = await getAvailableFiles(
|
|
118
|
+
path.join(contextPath, 'Infrastructure/Http/Datasource')
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const allDatasources = [
|
|
122
|
+
...sharedDatasources.map(d => ({ title: `${d} (shared)`, value: { name: d, from: 'shared' } })),
|
|
123
|
+
...contextDatasources.map(d => ({ title: `${d} (${context})`, value: { name: d, from: context } }))
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const { datasource } = await prompts({
|
|
127
|
+
type: 'select',
|
|
128
|
+
name: 'datasource',
|
|
129
|
+
message: 'Select datasource:',
|
|
130
|
+
choices: allDatasources
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const pascalName = toPascalCase(name);
|
|
134
|
+
const camelName = toCamelCase(name);
|
|
135
|
+
|
|
136
|
+
console.log('\nš Generating CRUD base (repository & service)...\n');
|
|
137
|
+
|
|
138
|
+
// 1. Create Entity
|
|
139
|
+
const entityPath = path.join(contextPath, 'Domain/Entities', pascalName);
|
|
140
|
+
await writeIfNotExists(
|
|
141
|
+
path.join(entityPath, 'index.ts'),
|
|
142
|
+
entityTemplate.replace(/{{name}}/g, pascalName)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// 2. Create Ports
|
|
146
|
+
const portsPath = path.join(contextPath, 'Domain/Ports', pascalName);
|
|
147
|
+
await writeIfNotExists(
|
|
148
|
+
path.join(portsPath, 'index.ts'),
|
|
149
|
+
portsIndexTemplate
|
|
150
|
+
.replace(/{{name}}/g, pascalName)
|
|
151
|
+
.replace(/{{context}}/g, context)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// 3. Create Repository
|
|
155
|
+
const repoPath = path.join(contextPath, 'Infrastructure/Http/Repositories');
|
|
156
|
+
const datasourceImport = datasource.from === 'shared'
|
|
157
|
+
? '$shared/Datasource'
|
|
158
|
+
: `\$${context}/Infrastructure/Http/Datasource`;
|
|
159
|
+
|
|
160
|
+
await writeIfNotExists(
|
|
161
|
+
path.join(repoPath, `${pascalName}Repository.ts`),
|
|
162
|
+
repositoryTemplate
|
|
163
|
+
.replace(/{{name}}/g, pascalName)
|
|
164
|
+
.replace(/{{context}}/g, context)
|
|
165
|
+
.replace(/{{endpoint}}/g, endpoint)
|
|
166
|
+
.replace(/{{datasource}}/g, datasource.name)
|
|
167
|
+
.replace(/{{datasourceImport}}/g, datasourceImport)
|
|
168
|
+
.replace(/{{datasourceVar}}/g, toCamelCase(datasource.name))
|
|
169
|
+
);
|
|
170
|
+
await updateIndexTs(repoPath);
|
|
171
|
+
|
|
172
|
+
// 4. Create Service
|
|
173
|
+
const servicePath = path.join(contextPath, 'Application/Services');
|
|
174
|
+
await writeIfNotExists(
|
|
175
|
+
path.join(servicePath, `${pascalName}Service.ts`),
|
|
176
|
+
serviceTemplate
|
|
177
|
+
.replace(/{{name}}/g, pascalName)
|
|
178
|
+
.replace(/{{camelName}}/g, camelName)
|
|
179
|
+
.replace(/{{context}}/g, context)
|
|
180
|
+
);
|
|
181
|
+
await updateIndexTs(servicePath);
|
|
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
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import { selectContext, getContextPath, toPascalCase, getAvailableFiles, getApplicationProviderServices } from '../../utils/contextUtils.js';
|
|
5
|
+
import { writeIfNotExists } from '../../utils/fileUtils.js';
|
|
6
|
+
|
|
7
|
+
const createSchemaTemplate = `import { {{schemaFactory}} } from '{{schemaImport}}';
|
|
8
|
+
import type { I{{name}}CreateRequest } from '\${{context}}/Domain/Ports/{{entity}}';
|
|
9
|
+
|
|
10
|
+
export const Create{{name}}Schema = {{schemaFactory}}<I{{name}}CreateRequest>()
|
|
11
|
+
\t.rules((rules) => ({
|
|
12
|
+
\t\t// Add validation rules here
|
|
13
|
+
\t}))
|
|
14
|
+
\t.create();`;
|
|
15
|
+
|
|
16
|
+
const updateSchemaTemplate = `import { {{schemaFactory}} } from '{{schemaImport}}';
|
|
17
|
+
import type { I{{name}}UpdateRequest } from '\${{context}}/Domain/Ports/{{entity}}';
|
|
18
|
+
|
|
19
|
+
export const Update{{name}}Schema = {{schemaFactory}}<I{{name}}UpdateRequest>()
|
|
20
|
+
\t.rules((rules) => ({
|
|
21
|
+
\t\t// Add validation rules here
|
|
22
|
+
\t}))
|
|
23
|
+
\t.create();`;
|
|
24
|
+
|
|
25
|
+
const presenterWithSharedTemplate = `import { {{presenterFactory}} } from '$shared/Presenter';
|
|
26
|
+
import { ApplicationProvider } from '\${{context}}/Application';
|
|
27
|
+
import { Create{{name}}Schema, Update{{name}}Schema } from './Schema';
|
|
28
|
+
import type {
|
|
29
|
+
\tI{{name}}CollectionQuery,
|
|
30
|
+
\tI{{name}}CreateRequest,
|
|
31
|
+
\tI{{name}}UpdateRequest
|
|
32
|
+
} from '\${{context}}/Domain/Ports/{{name}}';
|
|
33
|
+
|
|
34
|
+
export const {{name}}Presenter = {{presenterFactory}}('{{name}}Presenter', ({ createAsyncResource, createAsyncAction }) => {
|
|
35
|
+
\tconst { {{serviceName}} } = ApplicationProvider();
|
|
36
|
+
\t
|
|
37
|
+
\tconst collection = async (query?: I{{name}}CollectionQuery) =>
|
|
38
|
+
\t\tawait createAsyncResource({{serviceName}}.collection(query));
|
|
39
|
+
\t
|
|
40
|
+
\tconst resource = async (id: number) =>
|
|
41
|
+
\t\tawait createAsyncResource({{serviceName}}.resource(id));
|
|
42
|
+
\t
|
|
43
|
+
\tconst create = async (request: I{{name}}CreateRequest) =>
|
|
44
|
+
\t\tawait createAsyncAction({{serviceName}}.create(Create{{name}}Schema.from(request).json()));
|
|
45
|
+
\t
|
|
46
|
+
\tconst update = async (id: number, request: I{{name}}UpdateRequest) =>
|
|
47
|
+
\t\tawait createAsyncAction({{serviceName}}.update(id, Update{{name}}Schema.from(request).json()));
|
|
48
|
+
\t
|
|
49
|
+
\tconst remove = async (id: number) =>
|
|
50
|
+
\t\tawait createAsyncAction({{serviceName}}.remove(id));
|
|
51
|
+
\t
|
|
52
|
+
\treturn { collection, resource, create, update, remove };
|
|
53
|
+
});`;
|
|
54
|
+
|
|
55
|
+
const presenterWithoutSharedTemplate = `import { createPresenter } from '@azure-net/kit';
|
|
56
|
+
import { ApplicationProvider } from '\${{context}}/Application';
|
|
57
|
+
import { Create{{name}}Schema, Update{{name}}Schema } from './Schema';
|
|
58
|
+
import type {
|
|
59
|
+
\tI{{name}}CollectionQuery,
|
|
60
|
+
\tI{{name}}CreateRequest,
|
|
61
|
+
\tI{{name}}UpdateRequest
|
|
62
|
+
} from '\${{context}}/Domain/Ports/{{name}}';
|
|
63
|
+
|
|
64
|
+
export const {{name}}Presenter = createPresenter('{{name}}Presenter', () => {
|
|
65
|
+
\tconst { {{serviceName}} } = ApplicationProvider();
|
|
66
|
+
\t
|
|
67
|
+
\tconst collection = async (query?: I{{name}}CollectionQuery) =>
|
|
68
|
+
\t\tawait {{serviceName}}.collection(query);
|
|
69
|
+
\t
|
|
70
|
+
\tconst resource = async (id: number) =>
|
|
71
|
+
\t\tawait {{serviceName}}.resource(id);
|
|
72
|
+
\t
|
|
73
|
+
\tconst create = async (request: I{{name}}CreateRequest) =>
|
|
74
|
+
\t\tawait {{serviceName}}.create(Create{{name}}Schema.from(request).json());
|
|
75
|
+
\t
|
|
76
|
+
\tconst update = async (id: number, request: I{{name}}UpdateRequest) =>
|
|
77
|
+
\t\tawait {{serviceName}}.update(id, Update{{name}}Schema.from(request).json());
|
|
78
|
+
\t
|
|
79
|
+
\tconst remove = async (id: number) =>
|
|
80
|
+
\t\tawait {{serviceName}}.remove(id);
|
|
81
|
+
\t
|
|
82
|
+
\treturn { collection, resource, create, update, remove };
|
|
83
|
+
});`;
|
|
84
|
+
|
|
85
|
+
export default async function generateCrudPresenter() {
|
|
86
|
+
const context = await selectContext('Select context for CRUD presenter:');
|
|
87
|
+
|
|
88
|
+
// Get available services from ApplicationProvider
|
|
89
|
+
const services = await getApplicationProviderServices(context);
|
|
90
|
+
|
|
91
|
+
if (services.length === 0) {
|
|
92
|
+
console.error('ā No services found in ApplicationProvider. Add services manually first.');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { service } = await prompts({
|
|
97
|
+
type: 'select',
|
|
98
|
+
name: 'service',
|
|
99
|
+
message: 'Select service for CRUD:',
|
|
100
|
+
choices: services.map(s => ({ title: s, value: s }))
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Extract entity name from service name
|
|
104
|
+
const entityName = service.replace('Service', '');
|
|
105
|
+
const pascalName = toPascalCase(entityName);
|
|
106
|
+
|
|
107
|
+
// Get schema factory
|
|
108
|
+
const sharedSchemas = await getAvailableFiles(
|
|
109
|
+
path.join(process.cwd(), 'src/app/shared/Schema')
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const { schemaType } = await prompts({
|
|
113
|
+
type: 'select',
|
|
114
|
+
name: 'schemaType',
|
|
115
|
+
message: 'Select schema factory:',
|
|
116
|
+
choices: [
|
|
117
|
+
{ title: 'Use createSchemaFactory from package', value: 'package' },
|
|
118
|
+
...sharedSchemas.map(s => ({ title: `Use ${s} from shared`, value: s }))
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Get presenter factory
|
|
123
|
+
const sharedPresenters = await getAvailableFiles(
|
|
124
|
+
path.join(process.cwd(), 'src/app/shared/Presenter')
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const { presenterType } = await prompts({
|
|
128
|
+
type: 'select',
|
|
129
|
+
name: 'presenterType',
|
|
130
|
+
message: 'Select presenter factory:',
|
|
131
|
+
choices: [
|
|
132
|
+
{ title: 'Use createPresenter from package', value: 'package' },
|
|
133
|
+
...sharedPresenters.map(p => ({ title: `Use ${p} from shared`, value: p }))
|
|
134
|
+
]
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const contextPath = getContextPath(context);
|
|
138
|
+
const deliveryModulePath = path.join(contextPath, 'Delivery', pascalName);
|
|
139
|
+
const schemaPath = path.join(deliveryModulePath, 'Schema');
|
|
140
|
+
|
|
141
|
+
console.log('\nš Generating CRUD presenter...\n');
|
|
142
|
+
|
|
143
|
+
// Create schemas
|
|
144
|
+
const schemaFactory = schemaType === 'package' ? 'Schema' : schemaType;
|
|
145
|
+
const schemaImport = schemaType === 'package'
|
|
146
|
+
? '@azure-net/kit'
|
|
147
|
+
: '$shared/Schema';
|
|
148
|
+
|
|
149
|
+
if (schemaType === 'package') {
|
|
150
|
+
const schemaImportFull = `import { createSchemaFactory } from '@azure-net/kit';
|
|
151
|
+
import { createRules, validationMessagesI18n } from '@azure-net/kit/schema';
|
|
152
|
+
|
|
153
|
+
const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
|
|
154
|
+
|
|
155
|
+
await writeIfNotExists(
|
|
156
|
+
path.join(schemaPath, `Create${pascalName}Schema.ts`),
|
|
157
|
+
schemaImportFull + '\n' + createSchemaTemplate
|
|
158
|
+
.replace('import { {{schemaFactory}} } from \'{{schemaImport}}\';\n', '')
|
|
159
|
+
.replace(/{{name}}/g, pascalName)
|
|
160
|
+
.replace(/{{context}}/g, context)
|
|
161
|
+
.replace(/{{entity}}/g, pascalName)
|
|
162
|
+
.replace(/{{schemaFactory}}/g, 'Schema')
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
await writeIfNotExists(
|
|
166
|
+
path.join(schemaPath, `Update${pascalName}Schema.ts`),
|
|
167
|
+
schemaImportFull + '\n' + updateSchemaTemplate
|
|
168
|
+
.replace('import { {{schemaFactory}} } from \'{{schemaImport}}\';\n', '')
|
|
169
|
+
.replace(/{{name}}/g, pascalName)
|
|
170
|
+
.replace(/{{context}}/g, context)
|
|
171
|
+
.replace(/{{entity}}/g, pascalName)
|
|
172
|
+
.replace(/{{schemaFactory}}/g, 'Schema')
|
|
173
|
+
);
|
|
174
|
+
} else {
|
|
175
|
+
await writeIfNotExists(
|
|
176
|
+
path.join(schemaPath, `Create${pascalName}Schema.ts`),
|
|
177
|
+
createSchemaTemplate
|
|
178
|
+
.replace(/{{name}}/g, pascalName)
|
|
179
|
+
.replace(/{{context}}/g, context)
|
|
180
|
+
.replace(/{{entity}}/g, pascalName)
|
|
181
|
+
.replace(/{{schemaFactory}}/g, schemaFactory)
|
|
182
|
+
.replace(/{{schemaImport}}/g, schemaImport)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
await writeIfNotExists(
|
|
186
|
+
path.join(schemaPath, `Update${pascalName}Schema.ts`),
|
|
187
|
+
updateSchemaTemplate
|
|
188
|
+
.replace(/{{name}}/g, pascalName)
|
|
189
|
+
.replace(/{{context}}/g, context)
|
|
190
|
+
.replace(/{{entity}}/g, pascalName)
|
|
191
|
+
.replace(/{{schemaFactory}}/g, schemaFactory)
|
|
192
|
+
.replace(/{{schemaImport}}/g, schemaImport)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
await updateIndexTs(schemaPath);
|
|
196
|
+
|
|
197
|
+
// Create Presenter
|
|
198
|
+
const presenterTemplate = presenterType === 'package'
|
|
199
|
+
? presenterWithoutSharedTemplate
|
|
200
|
+
: presenterWithSharedTemplate;
|
|
201
|
+
|
|
202
|
+
const presenterFactory = presenterType === 'package' ? 'createPresenter' : presenterType;
|
|
203
|
+
|
|
204
|
+
await writeIfNotExists(
|
|
205
|
+
path.join(deliveryModulePath, `${pascalName}Presenter.ts`),
|
|
206
|
+
presenterTemplate
|
|
207
|
+
.replace(/{{name}}/g, pascalName)
|
|
208
|
+
.replace(/{{context}}/g, context)
|
|
209
|
+
.replace(/{{serviceName}}/g, service)
|
|
210
|
+
.replace(/{{presenterFactory}}/g, presenterFactory)
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Create module index
|
|
214
|
+
await writeIfNotExists(
|
|
215
|
+
path.join(deliveryModulePath, 'index.ts'),
|
|
216
|
+
`export * from './${pascalName}Presenter';\nexport * from './Schema';`
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
console.log(`ā
CRUD presenter for ${pascalName} created successfully!`);
|
|
220
|
+
}
|
|
@@ -1,32 +1,20 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { selectContext, getContextPath, toPascalCase, getAvailableFiles
|
|
3
|
+
import { selectContext, getContextPath, toPascalCase, getAvailableFiles } from '../../utils/contextUtils.js';
|
|
4
4
|
import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
|
|
5
|
-
import { ensureProvider, addToProvider } from '../../utils/providerUtils.js';
|
|
6
5
|
|
|
7
6
|
const datasourceWithResponseTemplate = `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
8
7
|
import { {{responseClass}}, type {{responseInterface}} } from '{{responseImport}}';
|
|
8
|
+
import { HttpService } from '@azure-net/kit/infra';
|
|
9
9
|
|
|
10
10
|
export class {{name}}Datasource extends BaseHttpDatasource {
|
|
11
|
-
\tconstructor(private http: HttpService) {
|
|
12
|
-
\t\tsuper({ http });
|
|
13
|
-
\t}
|
|
14
|
-
|
|
15
11
|
\tasync createRequest<T>(callback: CreateRequestCallbackType<{{responseInterface}}<T>>) {
|
|
16
12
|
\t\treturn new {{responseClass}}<T>(await this.createRawRequest<{{responseInterface}}<T>>(callback));
|
|
17
13
|
\t}
|
|
18
14
|
}`;
|
|
19
15
|
|
|
20
|
-
const datasourceWithoutResponseTemplate = `import { BaseHttpDatasource } from '@azure-net/kit/infra';
|
|
21
|
-
|
|
22
|
-
export class {{name}}Datasource extends BaseHttpDatasource {
|
|
23
|
-
\tconstructor(private http: HttpService) {
|
|
24
|
-
\t\tsuper({ http });
|
|
25
|
-
\t}
|
|
26
|
-
}`;
|
|
27
|
-
|
|
28
16
|
export default async function generateDatasource() {
|
|
29
|
-
const context = await selectContext();
|
|
17
|
+
const context = await selectContext('Select context for datasource (or shared):');
|
|
30
18
|
|
|
31
19
|
const { name } = await prompts({
|
|
32
20
|
type: 'text',
|
|
@@ -35,22 +23,23 @@ export default async function generateDatasource() {
|
|
|
35
23
|
});
|
|
36
24
|
|
|
37
25
|
const pascalName = toPascalCase(name);
|
|
38
|
-
const
|
|
39
|
-
|
|
26
|
+
const contextPath = context === 'shared'
|
|
27
|
+
? path.join(process.cwd(), 'src/app/shared')
|
|
28
|
+
: getContextPath(context);
|
|
40
29
|
|
|
41
30
|
// Get available responses
|
|
42
|
-
const
|
|
43
|
-
path.join(process.cwd(), 'src/app/
|
|
31
|
+
const sharedResponses = await getAvailableFiles(
|
|
32
|
+
path.join(process.cwd(), 'src/app/shared/Response')
|
|
44
33
|
);
|
|
45
34
|
|
|
46
|
-
const contextResponses = context !== '
|
|
35
|
+
const contextResponses = context !== 'shared'
|
|
47
36
|
? await getAvailableFiles(path.join(contextPath, 'Infrastructure/Response'))
|
|
48
37
|
: [];
|
|
49
38
|
|
|
50
39
|
const allResponses = [
|
|
51
|
-
...
|
|
52
|
-
title: `${r} (
|
|
53
|
-
value: { name: r, from: '
|
|
40
|
+
...sharedResponses.map(r => ({
|
|
41
|
+
title: `${r} (shared)`,
|
|
42
|
+
value: { name: r, from: 'shared' }
|
|
54
43
|
})),
|
|
55
44
|
...contextResponses.map(r => ({
|
|
56
45
|
title: `${r} (${context})`,
|
|
@@ -70,18 +59,16 @@ export default async function generateDatasource() {
|
|
|
70
59
|
choices: allResponses
|
|
71
60
|
});
|
|
72
61
|
|
|
73
|
-
// Determine interface name
|
|
62
|
+
// Determine interface name
|
|
74
63
|
const responseInterface = `I${response.name.replace('Response', '')}Response`;
|
|
75
64
|
|
|
76
|
-
const datasourcePath = context === '
|
|
65
|
+
const datasourcePath = context === 'shared'
|
|
77
66
|
? path.join(contextPath, 'Datasource')
|
|
78
|
-
: path.join(contextPath, 'Infrastructure
|
|
67
|
+
: path.join(contextPath, 'Infrastructure/Http/Datasource');
|
|
79
68
|
|
|
80
|
-
const responseImport = response.from === '
|
|
81
|
-
? '$
|
|
82
|
-
: context
|
|
83
|
-
? '../Response'
|
|
84
|
-
: '../../Response';
|
|
69
|
+
const responseImport = response.from === 'shared'
|
|
70
|
+
? context === 'shared' ? '../Response' : '$shared/Response'
|
|
71
|
+
: `\$${context}/Infrastructure/Response`;
|
|
85
72
|
|
|
86
73
|
const content = datasourceWithResponseTemplate
|
|
87
74
|
.replace(/{{name}}/g, pascalName)
|
|
@@ -93,63 +80,5 @@ export default async function generateDatasource() {
|
|
|
93
80
|
await writeIfNotExists(filePath, content);
|
|
94
81
|
await updateIndexTs(datasourcePath);
|
|
95
82
|
|
|
96
|
-
// If not in core, ensure DatasourceProvider exists
|
|
97
|
-
if (context !== 'core') {
|
|
98
|
-
const contextName = toPascalCase(context);
|
|
99
|
-
const providerPath = path.join(contextPath, 'Infrastructure/Providers/DatasourceProvider.ts');
|
|
100
|
-
|
|
101
|
-
if (!(await fs.access(providerPath).then(() => true).catch(() => false))) {
|
|
102
|
-
const providerContent = `import { createBoundaryProvider } from '@azure-net/kit';
|
|
103
|
-
import { HttpService } from '@azure-net/kit/infra';
|
|
104
|
-
import { ${pascalName}Datasource } from '../Http/Datasource';
|
|
105
|
-
|
|
106
|
-
export const DatasourceProvider = createBoundaryProvider('${contextName}DatasourceProvider', () => ({
|
|
107
|
-
\t${pascalName}Datasource: () => new ${pascalName}Datasource(
|
|
108
|
-
\t\tnew HttpService({ baseUrl: 'https://api.example.com' })
|
|
109
|
-
\t)
|
|
110
|
-
}));`;
|
|
111
|
-
await writeIfNotExists(providerPath, providerContent);
|
|
112
|
-
await updateIndexTs(path.dirname(providerPath));
|
|
113
|
-
} else {
|
|
114
|
-
// Add datasource to existing provider
|
|
115
|
-
await addDatasourceToProvider(providerPath, pascalName);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
83
|
console.log(`ā
Datasource created at ${filePath}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function addDatasourceToProvider(providerPath, datasourceName) {
|
|
123
|
-
let content = await fs.readFile(providerPath, 'utf-8');
|
|
124
|
-
|
|
125
|
-
// Add import if not exists
|
|
126
|
-
if (!content.includes(`${datasourceName}Datasource`)) {
|
|
127
|
-
const importStatement = `import { ${datasourceName}Datasource } from '../Http/Datasource';`;
|
|
128
|
-
const lastImportIndex = content.lastIndexOf('import ');
|
|
129
|
-
const endOfImportLine = content.indexOf('\n', lastImportIndex);
|
|
130
|
-
content = content.slice(0, endOfImportLine + 1) + importStatement + '\n' + content.slice(endOfImportLine + 1);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Add factory method
|
|
134
|
-
const factoryLine = `\t${datasourceName}Datasource: () => new ${datasourceName}Datasource(
|
|
135
|
-
\t\tnew HttpService({ baseUrl: 'https://api.example.com' })
|
|
136
|
-
\t)`;
|
|
137
|
-
|
|
138
|
-
if (!content.includes(`${datasourceName}Datasource:`)) {
|
|
139
|
-
const returnIndex = content.indexOf('=> ({');
|
|
140
|
-
const endOfReturn = content.indexOf('})', returnIndex);
|
|
141
|
-
const beforeEnd = content.lastIndexOf('\n', endOfReturn);
|
|
142
|
-
|
|
143
|
-
// Check if there are existing entries
|
|
144
|
-
const servicesSection = content.substring(returnIndex, endOfReturn);
|
|
145
|
-
const hasServices = servicesSection.includes(':');
|
|
146
|
-
|
|
147
|
-
if (hasServices) {
|
|
148
|
-
content = content.slice(0, beforeEnd) + ',\n' + factoryLine + content.slice(beforeEnd);
|
|
149
|
-
} else {
|
|
150
|
-
content = content.slice(0, beforeEnd + 1) + factoryLine + content.slice(beforeEnd);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
await fs.writeFile(providerPath, content, 'utf-8');
|
|
155
84
|
}
|
|
@@ -6,11 +6,13 @@ import generateSchema from './generateSchema.js';
|
|
|
6
6
|
|
|
7
7
|
export default async function generateModule() {
|
|
8
8
|
console.log('š Generating complete module...\n');
|
|
9
|
+
console.log('ā ļø Note: This will create repository, service, presenter and schemas.');
|
|
10
|
+
console.log(' You will need to manually wire them in the providers.\n');
|
|
9
11
|
|
|
10
12
|
const { confirm } = await prompts({
|
|
11
13
|
type: 'confirm',
|
|
12
14
|
name: 'confirm',
|
|
13
|
-
message: '
|
|
15
|
+
message: 'Continue?'
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
if (confirm) {
|
|
@@ -19,5 +21,11 @@ export default async function generateModule() {
|
|
|
19
21
|
await generatePresenter();
|
|
20
22
|
await generateSchema(); // Create schema
|
|
21
23
|
await generateSchema(); // Update schema
|
|
24
|
+
|
|
25
|
+
console.log('\nā
Module generation complete!');
|
|
26
|
+
console.log('\nš” Remember to:');
|
|
27
|
+
console.log(' 1. Add repository to InfrastructureProvider');
|
|
28
|
+
console.log(' 2. Add service to ApplicationProvider');
|
|
29
|
+
console.log(' 3. Update presenter to use the service from ApplicationProvider');
|
|
22
30
|
}
|
|
23
31
|
}
|