@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-net/cli",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "azure-net": "bin/azure-net.js"
@@ -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 for CRUD generation
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 '../../Entities/{{name}}';
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 '../../Infrastructure/Http/Repositories';
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 presenterTemplate = `import { {{presenterFactory}} } from '{{presenterImport}}';
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', 'Entities', pascalName);
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', 'Ports', pascalName);
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', 'Http', 'Repositories');
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', 'Services');
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
- const datasourceProviderPath = path.join(contextPath, 'Infrastructure', 'Providers', 'DatasourceProvider.ts');
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
- '../Http/Repositories',
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
- '../Services',
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 datasourceTemplate = `import { BaseHttpDatasource } from '@azure-net/kit/infra';
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', 'Http', 'Datasource');
75
+ : path.join(contextPath, 'Infrastructure/Http/Datasource');
33
76
 
34
- const content = datasourceTemplate.replace(/{{name}}/g, pascalName);
35
- const filePath = path.join(datasourcePath, `${pascalName}Datasource.ts`);
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// Implement presenter logic
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// Implement presenter logic
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', 'Services')
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: `${s}`, value: s }))
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', 'Http', 'Datasource')
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', 'Http', 'Repositories');
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
- const datasourceProviderPath = path.join(contextPath, 'Infrastructure', 'Providers', 'DatasourceProvider.ts');
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
- '../Http/Repositories',
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 (PascalCase):'
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, 'Responses')
33
- : path.join(contextPath, 'Infrastructure', 'Responses');
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
- return str
45
- .split(/[-_\s]+/)
46
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
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
- if (deps.length > 0) {
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 ? 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
- // Add import if not exists
72
- const importStatement = `import { ${serviceName} } from '${importPath}';`;
73
- if (!content.includes(serviceName)) {
74
- const lastImportIndex = content.lastIndexOf('import ');
75
- const endOfImportLine = content.indexOf('\n', lastImportIndex);
76
- content = content.slice(0, endOfImportLine + 1) + importStatement + '\n' + content.slice(endOfImportLine + 1);
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 (!content.includes(`${serviceName}:`)) {
137
+ if (!updatedAfterProvider.includes(`${serviceName}:`)) {
85
138
  // Find the position to insert
86
- const returnIndex = content.indexOf('=> ({');
87
- const endOfReturn = content.indexOf('\t})', returnIndex);
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 = content.substring(returnIndex, endOfReturn);
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 = content.lastIndexOf('\n', endOfReturn);
96
- content = content.slice(0, beforeEnd) + ',\n' + factoryLine + content.slice(beforeEnd);
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
- content = content.replace(
152
+ updatedAfterProvider = updatedAfterProvider.replace(
100
153
  '// Services will be added here',
101
154
  factoryLine
102
155
  );
103
156
  }
104
157
  }
105
158
 
106
- await fs.writeFile(providerPath, content, 'utf-8');
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
  }