@azure-net/cli 2.0.3 ā 2.0.5
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/package.json +1 -1
- package/src/commands/generate/generateCrud.js +51 -29
- package/src/commands/generate/generateDatasource.js +62 -8
- package/src/commands/generate/generatePresenter.js +5 -5
- package/src/commands/generate/generateRepository.js +5 -15
- package/src/commands/generate/generateResponse.js +5 -5
- package/src/utils/contextUtils.js +18 -3
- package/src/utils/providerUtils.js +168 -21
package/package.json
CHANGED
|
@@ -3,9 +3,9 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { selectContext, getContextPath, toPascalCase, toCamelCase, getAvailableFiles } from '../../utils/contextUtils.js';
|
|
5
5
|
import { writeIfNotExists, updateIndexTs, ensureIndexExports } from '../../utils/fileUtils.js';
|
|
6
|
-
import { ensureProvider, addToProvider } from '../../utils/providerUtils.js';
|
|
6
|
+
import { ensureProvider, addToProvider, ensureDatasourceProvider } from '../../utils/providerUtils.js';
|
|
7
7
|
|
|
8
|
-
// Templates
|
|
8
|
+
// Templates remain the same as before...
|
|
9
9
|
const entityTemplate = `export interface I{{name}} {
|
|
10
10
|
\tid: number;
|
|
11
11
|
\t// Add entity fields here
|
|
@@ -13,7 +13,7 @@ const entityTemplate = `export interface I{{name}} {
|
|
|
13
13
|
\tupdated_at: string;
|
|
14
14
|
}`;
|
|
15
15
|
|
|
16
|
-
const portsIndexTemplate = `import type { I{{name}} } from '
|
|
16
|
+
const portsIndexTemplate = `import type { I{{name}} } from '\${{context}}/Domain/Entities/{{name}}';
|
|
17
17
|
|
|
18
18
|
export interface I{{name}}Collection {
|
|
19
19
|
\tdata: I{{name}}[];
|
|
@@ -28,10 +28,12 @@ export interface I{{name}}CollectionQuery {
|
|
|
28
28
|
|
|
29
29
|
export interface I{{name}}CreateRequest {
|
|
30
30
|
\t// Add request fields here
|
|
31
|
+
\t[key: string]: unknown;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export interface I{{name}}UpdateRequest {
|
|
34
|
-
\t// Add request fields here
|
|
35
|
+
\t// Add request fields here
|
|
36
|
+
\t[key: string]: unknown;
|
|
35
37
|
}`;
|
|
36
38
|
|
|
37
39
|
const repositoryTemplate = `import { {{datasource}} } from '{{datasourceImport}}';
|
|
@@ -80,7 +82,7 @@ export class {{name}}Repository {
|
|
|
80
82
|
}`;
|
|
81
83
|
|
|
82
84
|
const serviceTemplate = `import { ClassMirror } from '@azure-net/kit';
|
|
83
|
-
import { {{name}}Repository } from '
|
|
85
|
+
import { {{name}}Repository } from '\${{context}}/Infrastructure/Http/Repositories';
|
|
84
86
|
|
|
85
87
|
export class {{name}}Service extends ClassMirror<{{name}}Repository> {
|
|
86
88
|
\tconstructor(private {{camelName}}Repository: {{name}}Repository) {
|
|
@@ -112,7 +114,7 @@ export const Update{{name}}Schema = {{schemaFactory}}<I{{name}}UpdateRequest>()
|
|
|
112
114
|
\t}))
|
|
113
115
|
\t.create();`;
|
|
114
116
|
|
|
115
|
-
const
|
|
117
|
+
const presenterWithCoreTemplate = `import { {{presenterFactory}} } from '\$core/Presenter';
|
|
116
118
|
import { ApplicationProvider } from '\${{context}}/Application';
|
|
117
119
|
import { Create{{name}}Schema, Update{{name}}Schema } from './Schema';
|
|
118
120
|
import type {
|
|
@@ -142,6 +144,36 @@ export const {{name}}Presenter = {{presenterFactory}}('{{name}}Presenter', ({ cr
|
|
|
142
144
|
\treturn { collection, resource, create, update, remove };
|
|
143
145
|
});`;
|
|
144
146
|
|
|
147
|
+
const presenterWithoutCoreTemplate = `import { createPresenter } from '@azure-net/kit';
|
|
148
|
+
import { ApplicationProvider } from '\${{context}}/Application';
|
|
149
|
+
import { Create{{name}}Schema, Update{{name}}Schema } from './Schema';
|
|
150
|
+
import type {
|
|
151
|
+
\tI{{name}}CollectionQuery,
|
|
152
|
+
\tI{{name}}CreateRequest,
|
|
153
|
+
\tI{{name}}UpdateRequest
|
|
154
|
+
} from '\${{context}}/Domain/Ports/{{name}}';
|
|
155
|
+
|
|
156
|
+
export const {{name}}Presenter = createPresenter('{{name}}Presenter', () => {
|
|
157
|
+
\tconst { {{name}}Service } = ApplicationProvider();
|
|
158
|
+
\t
|
|
159
|
+
\tconst collection = async (query?: I{{name}}CollectionQuery) =>
|
|
160
|
+
\t\tawait {{name}}Service.collection(query);
|
|
161
|
+
\t
|
|
162
|
+
\tconst resource = async (id: number) =>
|
|
163
|
+
\t\tawait {{name}}Service.resource(id);
|
|
164
|
+
\t
|
|
165
|
+
\tconst create = async (request: I{{name}}CreateRequest) =>
|
|
166
|
+
\t\tawait {{name}}Service.create(Create{{name}}Schema.from(request).json());
|
|
167
|
+
\t
|
|
168
|
+
\tconst update = async (id: number, request: I{{name}}UpdateRequest) =>
|
|
169
|
+
\t\tawait {{name}}Service.update(id, Update{{name}}Schema.from(request).json());
|
|
170
|
+
\t
|
|
171
|
+
\tconst remove = async (id: number) =>
|
|
172
|
+
\t\tawait {{name}}Service.remove(id);
|
|
173
|
+
\t
|
|
174
|
+
\treturn { collection, resource, create, update, remove };
|
|
175
|
+
});`;
|
|
176
|
+
|
|
145
177
|
export default async function generateCrud() {
|
|
146
178
|
const context = await selectContext('Select context for CRUD module:');
|
|
147
179
|
|
|
@@ -219,14 +251,14 @@ export default async function generateCrud() {
|
|
|
219
251
|
console.log('\nš Generating CRUD module...\n');
|
|
220
252
|
|
|
221
253
|
// 1. Create Entity
|
|
222
|
-
const entityPath = path.join(contextPath, 'Domain
|
|
254
|
+
const entityPath = path.join(contextPath, 'Domain/Entities', pascalName);
|
|
223
255
|
await writeIfNotExists(
|
|
224
256
|
path.join(entityPath, 'index.ts'),
|
|
225
257
|
entityTemplate.replace(/{{name}}/g, pascalName)
|
|
226
258
|
);
|
|
227
259
|
|
|
228
260
|
// 2. Create Ports
|
|
229
|
-
const portsPath = path.join(contextPath, 'Domain
|
|
261
|
+
const portsPath = path.join(contextPath, 'Domain/Ports', pascalName);
|
|
230
262
|
await writeIfNotExists(
|
|
231
263
|
path.join(portsPath, 'index.ts'),
|
|
232
264
|
portsIndexTemplate
|
|
@@ -235,7 +267,7 @@ export default async function generateCrud() {
|
|
|
235
267
|
);
|
|
236
268
|
|
|
237
269
|
// 3. Create Repository
|
|
238
|
-
const repoPath = path.join(contextPath, 'Infrastructure
|
|
270
|
+
const repoPath = path.join(contextPath, 'Infrastructure/Http/Repositories');
|
|
239
271
|
const datasourceImport = datasource.from === 'core'
|
|
240
272
|
? '$core/Datasource'
|
|
241
273
|
: `\$${context}/Infrastructure/Http/Datasource`;
|
|
@@ -253,12 +285,13 @@ export default async function generateCrud() {
|
|
|
253
285
|
await updateIndexTs(repoPath);
|
|
254
286
|
|
|
255
287
|
// 4. Create Service
|
|
256
|
-
const servicePath = path.join(contextPath, 'Application
|
|
288
|
+
const servicePath = path.join(contextPath, 'Application/Services');
|
|
257
289
|
await writeIfNotExists(
|
|
258
290
|
path.join(servicePath, `${pascalName}Service.ts`),
|
|
259
291
|
serviceTemplate
|
|
260
292
|
.replace(/{{name}}/g, pascalName)
|
|
261
293
|
.replace(/{{camelName}}/g, camelName)
|
|
294
|
+
.replace(/{{context}}/g, context)
|
|
262
295
|
);
|
|
263
296
|
await updateIndexTs(servicePath);
|
|
264
297
|
|
|
@@ -273,7 +306,6 @@ export default async function generateCrud() {
|
|
|
273
306
|
: '$core/Schema';
|
|
274
307
|
|
|
275
308
|
if (schemaType === 'package') {
|
|
276
|
-
// Add the import for createSchemaFactory and rules
|
|
277
309
|
const schemaImportFull = `import { createSchemaFactory } from '@azure-net/kit';
|
|
278
310
|
import { createRules, validationMessagesI18n } from '@azure-net/kit/schema';
|
|
279
311
|
|
|
@@ -318,10 +350,11 @@ const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
|
|
|
318
350
|
await updateIndexTs(schemaPath);
|
|
319
351
|
|
|
320
352
|
// 6. Create Presenter
|
|
353
|
+
const presenterTemplate = presenterType === 'package'
|
|
354
|
+
? presenterWithoutCoreTemplate
|
|
355
|
+
: presenterWithCoreTemplate;
|
|
356
|
+
|
|
321
357
|
const presenterFactory = presenterType === 'package' ? 'createPresenter' : presenterType;
|
|
322
|
-
const presenterImport = presenterType === 'package'
|
|
323
|
-
? '@azure-net/kit'
|
|
324
|
-
: '$core/Presenter';
|
|
325
358
|
|
|
326
359
|
await writeIfNotExists(
|
|
327
360
|
path.join(deliveryModulePath, `${pascalName}Presenter.ts`),
|
|
@@ -329,7 +362,6 @@ const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
|
|
|
329
362
|
.replace(/{{name}}/g, pascalName)
|
|
330
363
|
.replace(/{{context}}/g, context)
|
|
331
364
|
.replace(/{{presenterFactory}}/g, presenterFactory)
|
|
332
|
-
.replace(/{{presenterImport}}/g, presenterImport)
|
|
333
365
|
);
|
|
334
366
|
|
|
335
367
|
// Create module index
|
|
@@ -340,24 +372,14 @@ const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
|
|
|
340
372
|
|
|
341
373
|
// 7. Setup Providers
|
|
342
374
|
// Ensure DatasourceProvider
|
|
343
|
-
|
|
344
|
-
if (!(await fs.access(datasourceProviderPath).then(() => true).catch(() => false))) {
|
|
345
|
-
const datasourceProviderContent = `import { createBoundaryProvider } from '@azure-net/kit';
|
|
346
|
-
import { ${datasource.name} } from '${datasource.from === 'core' ? '$core/Datasource' : '../Http/Datasource'}';
|
|
347
|
-
|
|
348
|
-
export const DatasourceProvider = createBoundaryProvider('${contextName}DatasourceProvider', () => ({
|
|
349
|
-
\t${datasource.name}: () => new ${datasource.name}()
|
|
350
|
-
}));`;
|
|
351
|
-
await writeIfNotExists(datasourceProviderPath, datasourceProviderContent);
|
|
352
|
-
await updateIndexTs(path.dirname(datasourceProviderPath));
|
|
353
|
-
}
|
|
375
|
+
await ensureDatasourceProvider(context, datasource.name, datasource.from === 'core');
|
|
354
376
|
|
|
355
377
|
// Ensure InfrastructureProvider
|
|
356
|
-
const infraProvider = await ensureProvider(context, 'Infrastructure', {});
|
|
378
|
+
const infraProvider = await ensureProvider(context, 'Infrastructure', { hasDatasource: true });
|
|
357
379
|
await addToProvider(
|
|
358
380
|
infraProvider.path,
|
|
359
381
|
`${pascalName}Repository`,
|
|
360
|
-
|
|
382
|
+
`\$${context}/Infrastructure/Http/Repositories`,
|
|
361
383
|
`DatasourceProvider.${datasource.name}`
|
|
362
384
|
);
|
|
363
385
|
|
|
@@ -366,7 +388,7 @@ export const DatasourceProvider = createBoundaryProvider('${contextName}Datasour
|
|
|
366
388
|
await addToProvider(
|
|
367
389
|
appProvider.path,
|
|
368
390
|
`${pascalName}Service`,
|
|
369
|
-
|
|
391
|
+
`\$${context}/Application/Services`,
|
|
370
392
|
`InfrastructureProvider.${pascalName}Repository`
|
|
371
393
|
);
|
|
372
394
|
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
|
-
import { selectContext, getContextPath, toPascalCase } from '../../utils/contextUtils.js';
|
|
3
|
-
import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
|
|
4
2
|
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import { selectContext, getContextPath, toPascalCase, getAvailableFiles } from '../../utils/contextUtils.js';
|
|
5
|
+
import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const datasourceWithResponseTemplate = `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
|
|
8
|
+
import { {{responseClass}}, type {{responseInterface}} } from '{{responseImport}}';
|
|
7
9
|
import { HttpService } from '@azure-net/kit/infra';
|
|
8
10
|
|
|
9
11
|
export class {{name}}Datasource extends BaseHttpDatasource {
|
|
10
|
-
\tconstructor() {
|
|
12
|
+
\tconstructor(config?: { baseUrl?: string; [key: string]: unknown }) {
|
|
11
13
|
\t\tsuper({
|
|
12
14
|
\t\t\thttp: new HttpService({
|
|
13
|
-
\t\t\t\tbaseUrl: 'https://api.example.com'
|
|
15
|
+
\t\t\t\tbaseUrl: config?.baseUrl || 'https://api.example.com',
|
|
16
|
+
\t\t\t\t...config
|
|
14
17
|
\t\t\t})
|
|
15
18
|
\t\t});
|
|
16
19
|
\t}
|
|
20
|
+
|
|
21
|
+
\tasync createRequest<T>(callback: CreateRequestCallbackType<{{responseInterface}}<T>>) {
|
|
22
|
+
\t\treturn new {{responseClass}}<T>(await this.createRawRequest<{{responseInterface}}<T>>(callback));
|
|
23
|
+
\t}
|
|
17
24
|
}`;
|
|
18
25
|
|
|
19
26
|
export default async function generateDatasource() {
|
|
@@ -27,15 +34,62 @@ export default async function generateDatasource() {
|
|
|
27
34
|
|
|
28
35
|
const pascalName = toPascalCase(name);
|
|
29
36
|
const contextPath = getContextPath(context);
|
|
37
|
+
|
|
38
|
+
// Get available responses
|
|
39
|
+
const coreResponses = await getAvailableFiles(
|
|
40
|
+
path.join(process.cwd(), 'src/app/core/Response')
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const contextResponses = context !== 'core'
|
|
44
|
+
? await getAvailableFiles(path.join(contextPath, 'Infrastructure/Response'))
|
|
45
|
+
: [];
|
|
46
|
+
|
|
47
|
+
const allResponses = [
|
|
48
|
+
...coreResponses.map(r => ({
|
|
49
|
+
title: `${r} (core)`,
|
|
50
|
+
value: { name: r, from: 'core' }
|
|
51
|
+
})),
|
|
52
|
+
...contextResponses.map(r => ({
|
|
53
|
+
title: `${r} (${context})`,
|
|
54
|
+
value: { name: r, from: context }
|
|
55
|
+
}))
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
if (allResponses.length === 0) {
|
|
59
|
+
console.error('ā No responses available. Create a response first with make:response');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { response } = await prompts({
|
|
64
|
+
type: 'select',
|
|
65
|
+
name: 'response',
|
|
66
|
+
message: 'Select response for datasource:',
|
|
67
|
+
choices: allResponses
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Determine interface name
|
|
71
|
+
const responseInterface = `I${response.name.replace('Response', '')}Response`;
|
|
72
|
+
|
|
30
73
|
const datasourcePath = context === 'core'
|
|
31
74
|
? path.join(contextPath, 'Datasource')
|
|
32
|
-
: path.join(contextPath, 'Infrastructure
|
|
75
|
+
: path.join(contextPath, 'Infrastructure/Http/Datasource');
|
|
33
76
|
|
|
34
|
-
const
|
|
35
|
-
|
|
77
|
+
const responseImport = response.from === 'core'
|
|
78
|
+
? context === 'core' ? '../Response' : '$core/Response'
|
|
79
|
+
: context === 'core'
|
|
80
|
+
? `../contexts/${context}/Infrastructure/Response`
|
|
81
|
+
: '../../Response';
|
|
82
|
+
|
|
83
|
+
const content = datasourceWithResponseTemplate
|
|
84
|
+
.replace(/{{name}}/g, pascalName)
|
|
85
|
+
.replace(/{{responseClass}}/g, response.name)
|
|
86
|
+
.replace(/{{responseInterface}}/g, responseInterface)
|
|
87
|
+
.replace(/{{responseImport}}/g, responseImport);
|
|
36
88
|
|
|
89
|
+
const filePath = path.join(datasourcePath, `${pascalName}Datasource.ts`);
|
|
37
90
|
await writeIfNotExists(filePath, content);
|
|
38
91
|
await updateIndexTs(datasourcePath);
|
|
39
92
|
|
|
40
93
|
console.log(`ā
Datasource created at ${filePath}`);
|
|
94
|
+
console.log(`\nš” Remember to configure the datasource in DatasourceProvider when using it in repositories.`);
|
|
41
95
|
}
|
|
@@ -10,7 +10,7 @@ export const {{name}}Presenter = {{corePresenter}}('{{name}}Presenter', ({ creat
|
|
|
10
10
|
\t{{serviceInit}}
|
|
11
11
|
\t
|
|
12
12
|
\tconst example = async () => {
|
|
13
|
-
\t\t//
|
|
13
|
+
\t\t// Using async helpers from core presenter factory
|
|
14
14
|
\t\treturn await createAsyncResource(Promise.resolve({}));
|
|
15
15
|
\t};
|
|
16
16
|
\t
|
|
@@ -24,8 +24,8 @@ export const {{name}}Presenter = createPresenter('{{name}}Presenter', () => {
|
|
|
24
24
|
\t{{serviceInit}}
|
|
25
25
|
\t
|
|
26
26
|
\tconst example = async () => {
|
|
27
|
-
\t\t//
|
|
28
|
-
\t\treturn {};
|
|
27
|
+
\t\t// Direct method without async helpers
|
|
28
|
+
\t\treturn await Promise.resolve({});
|
|
29
29
|
\t};
|
|
30
30
|
\t
|
|
31
31
|
\treturn { example };
|
|
@@ -65,12 +65,12 @@ export default async function generatePresenter() {
|
|
|
65
65
|
// Get available services
|
|
66
66
|
const contextPath = getContextPath(context);
|
|
67
67
|
const services = await getAvailableFiles(
|
|
68
|
-
path.join(contextPath, 'Application
|
|
68
|
+
path.join(contextPath, 'Application/Services')
|
|
69
69
|
);
|
|
70
70
|
|
|
71
71
|
const serviceChoices = [
|
|
72
72
|
{ title: 'Without service', value: null },
|
|
73
|
-
...services.map(s => ({ title:
|
|
73
|
+
...services.map(s => ({ title: s, value: s }))
|
|
74
74
|
];
|
|
75
75
|
|
|
76
76
|
const { service } = await prompts({
|
|
@@ -3,7 +3,7 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { selectContext, getContextPath, toPascalCase, getAvailableFiles, toCamelCase } from '../../utils/contextUtils.js';
|
|
5
5
|
import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
|
|
6
|
-
import { ensureProvider, addToProvider } from '../../utils/providerUtils.js';
|
|
6
|
+
import { ensureProvider, addToProvider, ensureDatasourceProvider } from '../../utils/providerUtils.js';
|
|
7
7
|
|
|
8
8
|
const repositoryTemplate = `import { {{datasource}} } from '{{datasourceImport}}';
|
|
9
9
|
|
|
@@ -37,7 +37,7 @@ export default async function generateRepository() {
|
|
|
37
37
|
path.join(process.cwd(), 'src/app/core/Datasource')
|
|
38
38
|
);
|
|
39
39
|
const contextDatasources = await getAvailableFiles(
|
|
40
|
-
path.join(contextPath, 'Infrastructure
|
|
40
|
+
path.join(contextPath, 'Infrastructure/Http/Datasource')
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
const allDatasources = [
|
|
@@ -59,7 +59,7 @@ export default async function generateRepository() {
|
|
|
59
59
|
|
|
60
60
|
const pascalName = toPascalCase(name);
|
|
61
61
|
const contextName = toPascalCase(context);
|
|
62
|
-
const repoPath = path.join(contextPath, 'Infrastructure
|
|
62
|
+
const repoPath = path.join(contextPath, 'Infrastructure/Http/Repositories');
|
|
63
63
|
const filePath = path.join(repoPath, `${pascalName}Repository.ts`);
|
|
64
64
|
|
|
65
65
|
// Generate repository
|
|
@@ -77,24 +77,14 @@ export default async function generateRepository() {
|
|
|
77
77
|
await updateIndexTs(repoPath);
|
|
78
78
|
|
|
79
79
|
// Ensure DatasourceProvider exists
|
|
80
|
-
|
|
81
|
-
if (!(await fs.access(datasourceProviderPath).then(() => true).catch(() => false))) {
|
|
82
|
-
const datasourceProviderContent = `import { createBoundaryProvider } from '@azure-net/kit';
|
|
83
|
-
import { ${datasource.name} } from '${datasource.from === 'core' ? '$core/Datasource' : '../Http/Datasource'}';
|
|
84
|
-
|
|
85
|
-
export const DatasourceProvider = createBoundaryProvider('${contextName}DatasourceProvider', () => ({
|
|
86
|
-
\t${datasource.name}: () => new ${datasource.name}()
|
|
87
|
-
}));`;
|
|
88
|
-
await writeIfNotExists(datasourceProviderPath, datasourceProviderContent);
|
|
89
|
-
await updateIndexTs(path.dirname(datasourceProviderPath));
|
|
90
|
-
}
|
|
80
|
+
await ensureDatasourceProvider(context, datasource.name, datasource.from === 'core');
|
|
91
81
|
|
|
92
82
|
// Ensure InfrastructureProvider and add repository
|
|
93
83
|
const infraProvider = await ensureProvider(context, 'Infrastructure', { hasDatasource: true });
|
|
94
84
|
await addToProvider(
|
|
95
85
|
infraProvider.path,
|
|
96
86
|
`${pascalName}Repository`,
|
|
97
|
-
|
|
87
|
+
`\$${context}/Infrastructure/Http/Repositories`,
|
|
98
88
|
`DatasourceProvider.${datasource.name}`
|
|
99
89
|
);
|
|
100
90
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
|
+
import path from 'path';
|
|
2
3
|
import { selectContext, getContextPath, toPascalCase } from '../../utils/contextUtils.js';
|
|
3
4
|
import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
|
|
4
|
-
import path from 'path';
|
|
5
5
|
|
|
6
|
-
const responseTemplate = `import { ResponseBuilder } from '@azure-net/kit';
|
|
6
|
+
const responseTemplate = `import { ResponseBuilder } from '@azure-net/kit/infra';
|
|
7
7
|
|
|
8
8
|
export interface I{{name}}Response<T = unknown> {
|
|
9
9
|
\tdata: T;
|
|
@@ -23,14 +23,14 @@ export default async function generateResponse() {
|
|
|
23
23
|
const { name } = await prompts({
|
|
24
24
|
type: 'text',
|
|
25
25
|
name: 'name',
|
|
26
|
-
message: 'Response name (
|
|
26
|
+
message: 'Response name (without "Response" suffix):'
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const pascalName = toPascalCase(name);
|
|
30
30
|
const contextPath = getContextPath(context);
|
|
31
31
|
const responsePath = context === 'core'
|
|
32
|
-
? path.join(contextPath, '
|
|
33
|
-
: path.join(contextPath, 'Infrastructure'
|
|
32
|
+
? path.join(contextPath, 'Response')
|
|
33
|
+
: path.join(contextPath, 'Infrastructure/Response');
|
|
34
34
|
|
|
35
35
|
const content = responseTemplate.replace(/{{name}}/g, pascalName);
|
|
36
36
|
const filePath = path.join(responsePath, `${pascalName}Response.ts`);
|
|
@@ -40,13 +40,28 @@ export async function getAvailableFiles(dir, pattern = '.ts') {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Fixed to preserve PascalCase properly
|
|
43
44
|
export function toPascalCase(str) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// If already in PascalCase with multiple words, preserve it
|
|
46
|
+
if (/^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/.test(str)) {
|
|
47
|
+
return str;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Split by common delimiters but preserve existing camelCase/PascalCase
|
|
51
|
+
const words = str.split(/[-_\s]+/);
|
|
52
|
+
return words
|
|
53
|
+
.map((word, index) => {
|
|
54
|
+
// If word is already in camelCase or PascalCase, preserve it
|
|
55
|
+
if (/[a-z][A-Z]/.test(word)) {
|
|
56
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
57
|
+
}
|
|
58
|
+
// Otherwise capitalize first letter
|
|
59
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
60
|
+
})
|
|
47
61
|
.join('');
|
|
48
62
|
}
|
|
49
63
|
|
|
64
|
+
// Fixed to preserve camelCase properly
|
|
50
65
|
export function toCamelCase(str) {
|
|
51
66
|
const pascal = toPascalCase(str);
|
|
52
67
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
@@ -35,7 +35,6 @@ function generateProviderContent(context, layer, dependencies) {
|
|
|
35
35
|
|
|
36
36
|
let imports = [`import { createBoundaryProvider } from '@azure-net/kit';`];
|
|
37
37
|
let deps = [];
|
|
38
|
-
let depParams = '';
|
|
39
38
|
|
|
40
39
|
if (dependencies.hasDatasource && layer === 'Infrastructure') {
|
|
41
40
|
imports.push(`import { DatasourceProvider } from './DatasourceProvider';`);
|
|
@@ -47,10 +46,7 @@ function generateProviderContent(context, layer, dependencies) {
|
|
|
47
46
|
deps.push('InfrastructureProvider');
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
depParams = `{ ${deps.join(', ')} }`;
|
|
52
|
-
}
|
|
53
|
-
|
|
49
|
+
const depParams = deps.length > 0 ? `{ ${deps.join(', ')} }` : '';
|
|
54
50
|
const dependsOnStr = deps.length > 0
|
|
55
51
|
? `,\n\t{ dependsOn: { ${deps.join(', ')} } }`
|
|
56
52
|
: '';
|
|
@@ -59,7 +55,7 @@ function generateProviderContent(context, layer, dependencies) {
|
|
|
59
55
|
|
|
60
56
|
export const ${varName} = createBoundaryProvider(
|
|
61
57
|
\t'${boundaryName}',
|
|
62
|
-
\t(${depParams
|
|
58
|
+
\t(${depParams}) => ({
|
|
63
59
|
\t\t// Services will be added here
|
|
64
60
|
\t})${dependsOnStr}
|
|
65
61
|
);`;
|
|
@@ -68,12 +64,69 @@ export const ${varName} = createBoundaryProvider(
|
|
|
68
64
|
export async function addToProvider(providerPath, serviceName, importPath, dependency = null) {
|
|
69
65
|
let content = await fs.readFile(providerPath, 'utf-8');
|
|
70
66
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
// Parse existing imports to group them
|
|
68
|
+
const importRegex = /^import\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"]/gm;
|
|
69
|
+
const imports = new Map();
|
|
70
|
+
let match;
|
|
71
|
+
|
|
72
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
73
|
+
const [fullMatch, importedItems, fromPath] = match;
|
|
74
|
+
if (!imports.has(fromPath)) {
|
|
75
|
+
imports.set(fromPath, new Set());
|
|
76
|
+
}
|
|
77
|
+
importedItems.split(',').forEach(item => {
|
|
78
|
+
imports.get(fromPath).add(item.trim());
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Add new import to the map
|
|
83
|
+
if (!Array.from(imports.values()).some(set => set.has(serviceName))) {
|
|
84
|
+
if (!imports.has(importPath)) {
|
|
85
|
+
imports.set(importPath, new Set());
|
|
86
|
+
}
|
|
87
|
+
imports.get(importPath).add(serviceName);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Rebuild import section
|
|
91
|
+
const newImports = [`import { createBoundaryProvider } from '@azure-net/kit';`];
|
|
92
|
+
|
|
93
|
+
// Add DatasourceProvider import if exists
|
|
94
|
+
if (content.includes('DatasourceProvider') && !content.includes("import { DatasourceProvider }")) {
|
|
95
|
+
newImports.push(`import { DatasourceProvider } from './DatasourceProvider';`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Add other imports
|
|
99
|
+
for (const [fromPath, items] of imports) {
|
|
100
|
+
if (fromPath !== '@azure-net/kit' && !fromPath.includes('DatasourceProvider')) {
|
|
101
|
+
const itemsStr = Array.from(items).join(', ');
|
|
102
|
+
newImports.push(`import { ${itemsStr} } from '${fromPath}';`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Find where imports end and provider starts
|
|
107
|
+
const providerStartIndex = content.indexOf('export const');
|
|
108
|
+
const beforeProvider = content.substring(0, providerStartIndex);
|
|
109
|
+
const afterProvider = content.substring(providerStartIndex);
|
|
110
|
+
|
|
111
|
+
// Check if DatasourceProvider dependency needs to be added
|
|
112
|
+
let updatedAfterProvider = afterProvider;
|
|
113
|
+
if (dependency && dependency.includes('DatasourceProvider')) {
|
|
114
|
+
// Check if provider already has dependencies
|
|
115
|
+
if (!afterProvider.includes('({ DatasourceProvider })')) {
|
|
116
|
+
// Add DatasourceProvider to function params
|
|
117
|
+
updatedAfterProvider = updatedAfterProvider.replace(
|
|
118
|
+
/createBoundaryProvider\(\s*'[^']+',\s*\(\)/,
|
|
119
|
+
`createBoundaryProvider(\n\t'$1',\n\t({ DatasourceProvider })`
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Add dependsOn if not exists
|
|
123
|
+
if (!updatedAfterProvider.includes('dependsOn')) {
|
|
124
|
+
updatedAfterProvider = updatedAfterProvider.replace(
|
|
125
|
+
/\)\s*\);$/,
|
|
126
|
+
`),\n\t{ dependsOn: { DatasourceProvider } }\n);`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
77
130
|
}
|
|
78
131
|
|
|
79
132
|
// Add factory method with dependency
|
|
@@ -81,27 +134,121 @@ export async function addToProvider(providerPath, serviceName, importPath, depen
|
|
|
81
134
|
? `\t\t${serviceName}: () => new ${serviceName}(${dependency})`
|
|
82
135
|
: `\t\t${serviceName}: () => new ${serviceName}()`;
|
|
83
136
|
|
|
84
|
-
if (!
|
|
137
|
+
if (!updatedAfterProvider.includes(`${serviceName}:`)) {
|
|
85
138
|
// Find the position to insert
|
|
86
|
-
const returnIndex =
|
|
87
|
-
const endOfReturn =
|
|
139
|
+
const returnIndex = updatedAfterProvider.indexOf('=> ({');
|
|
140
|
+
const endOfReturn = updatedAfterProvider.indexOf('\t})', returnIndex);
|
|
88
141
|
|
|
89
142
|
// Check if there are existing services
|
|
90
|
-
const servicesSection =
|
|
91
|
-
const hasServices = servicesSection.includes(':');
|
|
143
|
+
const servicesSection = updatedAfterProvider.substring(returnIndex, endOfReturn);
|
|
144
|
+
const hasServices = servicesSection.includes(':') && !servicesSection.includes('// Services will be added here');
|
|
92
145
|
|
|
93
146
|
if (hasServices) {
|
|
94
147
|
// Add comma before new service
|
|
95
|
-
const beforeEnd =
|
|
96
|
-
|
|
148
|
+
const beforeEnd = updatedAfterProvider.lastIndexOf('\n', endOfReturn);
|
|
149
|
+
updatedAfterProvider = updatedAfterProvider.slice(0, beforeEnd) + ',\n' + factoryLine + updatedAfterProvider.slice(beforeEnd);
|
|
97
150
|
} else {
|
|
98
151
|
// First service, replace comment
|
|
99
|
-
|
|
152
|
+
updatedAfterProvider = updatedAfterProvider.replace(
|
|
100
153
|
'// Services will be added here',
|
|
101
154
|
factoryLine
|
|
102
155
|
);
|
|
103
156
|
}
|
|
104
157
|
}
|
|
105
158
|
|
|
106
|
-
|
|
159
|
+
// Combine everything
|
|
160
|
+
const finalContent = newImports.join('\n') + '\n\n' + updatedAfterProvider;
|
|
161
|
+
await fs.writeFile(providerPath, finalContent, 'utf-8');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function ensureDatasourceProvider(context, datasourceName, isFromCore = true) {
|
|
165
|
+
const contextName = toPascalCase(context);
|
|
166
|
+
const contextPath = path.join(process.cwd(), 'src/app/contexts', context);
|
|
167
|
+
const providerPath = path.join(contextPath, 'Infrastructure/Providers/DatasourceProvider.ts');
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await fs.access(providerPath);
|
|
171
|
+
// Provider exists, add datasource if needed
|
|
172
|
+
await addDatasourceToProvider(providerPath, datasourceName, isFromCore);
|
|
173
|
+
} catch {
|
|
174
|
+
// Create new DatasourceProvider
|
|
175
|
+
const content = `import { createBoundaryProvider } from '@azure-net/kit';
|
|
176
|
+
import { ${datasourceName} } from '${isFromCore ? '$core' : '../Http/Datasource'}';
|
|
177
|
+
|
|
178
|
+
export const DatasourceProvider = createBoundaryProvider('${contextName}DatasourceProvider', () => ({
|
|
179
|
+
\t${datasourceName}: () => new ${datasourceName}({})
|
|
180
|
+
}));`;
|
|
181
|
+
|
|
182
|
+
await writeIfNotExists(providerPath, content);
|
|
183
|
+
await updateIndexTs(path.dirname(providerPath));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function addDatasourceToProvider(providerPath, datasourceName, isFromCore = true) {
|
|
188
|
+
let content = await fs.readFile(providerPath, 'utf-8');
|
|
189
|
+
|
|
190
|
+
// Check if datasource already exists
|
|
191
|
+
if (content.includes(`${datasourceName}:`)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Parse existing imports to group them
|
|
196
|
+
const importRegex = /^import\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"]/gm;
|
|
197
|
+
const imports = new Map();
|
|
198
|
+
let match;
|
|
199
|
+
|
|
200
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
201
|
+
const [fullMatch, importedItems, fromPath] = match;
|
|
202
|
+
if (!imports.has(fromPath)) {
|
|
203
|
+
imports.set(fromPath, new Set());
|
|
204
|
+
}
|
|
205
|
+
importedItems.split(',').forEach(item => {
|
|
206
|
+
imports.get(fromPath).add(item.trim());
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Add datasource to imports
|
|
211
|
+
const importPath = isFromCore ? '$core' : '../Http/Datasource';
|
|
212
|
+
if (!imports.has(importPath)) {
|
|
213
|
+
imports.set(importPath, new Set());
|
|
214
|
+
}
|
|
215
|
+
imports.get(importPath).add(datasourceName);
|
|
216
|
+
|
|
217
|
+
// Rebuild import section
|
|
218
|
+
const newImports = [`import { createBoundaryProvider } from '@azure-net/kit';`];
|
|
219
|
+
for (const [fromPath, items] of imports) {
|
|
220
|
+
if (fromPath !== '@azure-net/kit') {
|
|
221
|
+
const itemsStr = Array.from(items).join(', ');
|
|
222
|
+
newImports.push(`import { ${itemsStr} } from '${fromPath}';`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Find provider content
|
|
227
|
+
const providerStartIndex = content.indexOf('export const');
|
|
228
|
+
const afterProvider = content.substring(providerStartIndex);
|
|
229
|
+
|
|
230
|
+
// Add factory method
|
|
231
|
+
const factoryLine = `\t${datasourceName}: () => new ${datasourceName}({})`;
|
|
232
|
+
|
|
233
|
+
const returnIndex = afterProvider.indexOf('=> ({');
|
|
234
|
+
const endOfReturn = afterProvider.indexOf('})', returnIndex);
|
|
235
|
+
|
|
236
|
+
// Check if there are existing services
|
|
237
|
+
const servicesSection = afterProvider.substring(returnIndex, endOfReturn);
|
|
238
|
+
const hasServices = servicesSection.includes(':');
|
|
239
|
+
|
|
240
|
+
let updatedAfterProvider = afterProvider;
|
|
241
|
+
if (hasServices) {
|
|
242
|
+
// Add comma before new service
|
|
243
|
+
const beforeEnd = updatedAfterProvider.lastIndexOf('\n', endOfReturn);
|
|
244
|
+
updatedAfterProvider = updatedAfterProvider.slice(0, beforeEnd) + ',\n' + factoryLine + updatedAfterProvider.slice(beforeEnd);
|
|
245
|
+
} else {
|
|
246
|
+
// First service
|
|
247
|
+
const insertPoint = returnIndex + '=> ({\n'.length;
|
|
248
|
+
updatedAfterProvider = updatedAfterProvider.slice(0, insertPoint) + factoryLine + '\n' + updatedAfterProvider.slice(insertPoint);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Combine everything
|
|
252
|
+
const finalContent = newImports.join('\n') + '\n\n' + updatedAfterProvider;
|
|
253
|
+
await fs.writeFile(providerPath, finalContent, 'utf-8');
|
|
107
254
|
}
|