@azure-net/cli 2.0.2 → 2.0.4

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.2",
3
+ "version": "2.0.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "azure-net": "bin/azure-net.js"
@@ -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,8 @@ 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
+ // Presenter template with core factory (includes async helpers)
118
+ const presenterWithCoreTemplate = `import { {{presenterFactory}} } from '\$core/Presenter';
116
119
  import { ApplicationProvider } from '\${{context}}/Application';
117
120
  import { Create{{name}}Schema, Update{{name}}Schema } from './Schema';
118
121
  import type {
@@ -142,6 +145,37 @@ export const {{name}}Presenter = {{presenterFactory}}('{{name}}Presenter', ({ cr
142
145
  \treturn { collection, resource, create, update, remove };
143
146
  });`;
144
147
 
148
+ // Presenter template without async helpers
149
+ const presenterWithoutCoreTemplate = `import { createPresenter } from '@azure-net/kit';
150
+ import { ApplicationProvider } from '\${{context}}/Application';
151
+ import { Create{{name}}Schema, Update{{name}}Schema } from './Schema';
152
+ import type {
153
+ \tI{{name}}CollectionQuery,
154
+ \tI{{name}}CreateRequest,
155
+ \tI{{name}}UpdateRequest
156
+ } from '\${{context}}/Domain/Ports/{{name}}';
157
+
158
+ export const {{name}}Presenter = createPresenter('{{name}}Presenter', () => {
159
+ \tconst { {{name}}Service } = ApplicationProvider();
160
+ \t
161
+ \tconst collection = async (query?: I{{name}}CollectionQuery) =>
162
+ \t\tawait {{name}}Service.collection(query);
163
+ \t
164
+ \tconst resource = async (id: number) =>
165
+ \t\tawait {{name}}Service.resource(id);
166
+ \t
167
+ \tconst create = async (request: I{{name}}CreateRequest) =>
168
+ \t\tawait {{name}}Service.create(Create{{name}}Schema.from(request).json());
169
+ \t
170
+ \tconst update = async (id: number, request: I{{name}}UpdateRequest) =>
171
+ \t\tawait {{name}}Service.update(id, Update{{name}}Schema.from(request).json());
172
+ \t
173
+ \tconst remove = async (id: number) =>
174
+ \t\tawait {{name}}Service.remove(id);
175
+ \t
176
+ \treturn { collection, resource, create, update, remove };
177
+ });`;
178
+
145
179
  export default async function generateCrud() {
146
180
  const context = await selectContext('Select context for CRUD module:');
147
181
 
@@ -219,14 +253,14 @@ export default async function generateCrud() {
219
253
  console.log('\nšŸš€ Generating CRUD module...\n');
220
254
 
221
255
  // 1. Create Entity
222
- const entityPath = path.join(contextPath, 'Domain', 'Entities', pascalName);
256
+ const entityPath = path.join(contextPath, 'Domain/Entities', pascalName);
223
257
  await writeIfNotExists(
224
258
  path.join(entityPath, 'index.ts'),
225
259
  entityTemplate.replace(/{{name}}/g, pascalName)
226
260
  );
227
261
 
228
262
  // 2. Create Ports
229
- const portsPath = path.join(contextPath, 'Domain', 'Ports', pascalName);
263
+ const portsPath = path.join(contextPath, 'Domain/Ports', pascalName);
230
264
  await writeIfNotExists(
231
265
  path.join(portsPath, 'index.ts'),
232
266
  portsIndexTemplate
@@ -235,7 +269,7 @@ export default async function generateCrud() {
235
269
  );
236
270
 
237
271
  // 3. Create Repository
238
- const repoPath = path.join(contextPath, 'Infrastructure', 'Http', 'Repositories');
272
+ const repoPath = path.join(contextPath, 'Infrastructure/Http/Repositories');
239
273
  const datasourceImport = datasource.from === 'core'
240
274
  ? '$core/Datasource'
241
275
  : `\$${context}/Infrastructure/Http/Datasource`;
@@ -253,12 +287,13 @@ export default async function generateCrud() {
253
287
  await updateIndexTs(repoPath);
254
288
 
255
289
  // 4. Create Service
256
- const servicePath = path.join(contextPath, 'Application', 'Services');
290
+ const servicePath = path.join(contextPath, 'Application/Services');
257
291
  await writeIfNotExists(
258
292
  path.join(servicePath, `${pascalName}Service.ts`),
259
293
  serviceTemplate
260
294
  .replace(/{{name}}/g, pascalName)
261
295
  .replace(/{{camelName}}/g, camelName)
296
+ .replace(/{{context}}/g, context)
262
297
  );
263
298
  await updateIndexTs(servicePath);
264
299
 
@@ -317,11 +352,12 @@ const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
317
352
  }
318
353
  await updateIndexTs(schemaPath);
319
354
 
320
- // 6. Create Presenter
355
+ // 6. Create Presenter - use correct template based on type
356
+ const presenterTemplate = presenterType === 'package'
357
+ ? presenterWithoutCoreTemplate
358
+ : presenterWithCoreTemplate;
359
+
321
360
  const presenterFactory = presenterType === 'package' ? 'createPresenter' : presenterType;
322
- const presenterImport = presenterType === 'package'
323
- ? '@azure-net/kit'
324
- : '$core/Presenter';
325
361
 
326
362
  await writeIfNotExists(
327
363
  path.join(deliveryModulePath, `${pascalName}Presenter.ts`),
@@ -329,7 +365,6 @@ const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
329
365
  .replace(/{{name}}/g, pascalName)
330
366
  .replace(/{{context}}/g, context)
331
367
  .replace(/{{presenterFactory}}/g, presenterFactory)
332
- .replace(/{{presenterImport}}/g, presenterImport)
333
368
  );
334
369
 
335
370
  // Create module index
@@ -340,13 +375,13 @@ const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
340
375
 
341
376
  // 7. Setup Providers
342
377
  // Ensure DatasourceProvider
343
- const datasourceProviderPath = path.join(contextPath, 'Infrastructure', 'Providers', 'DatasourceProvider.ts');
378
+ const datasourceProviderPath = path.join(contextPath, 'Infrastructure/Providers/DatasourceProvider.ts');
344
379
  if (!(await fs.access(datasourceProviderPath).then(() => true).catch(() => false))) {
345
380
  const datasourceProviderContent = `import { createBoundaryProvider } from '@azure-net/kit';
346
381
  import { ${datasource.name} } from '${datasource.from === 'core' ? '$core/Datasource' : '../Http/Datasource'}';
347
382
 
348
383
  export const DatasourceProvider = createBoundaryProvider('${contextName}DatasourceProvider', () => ({
349
- \t${datasource.name}: () => new ${datasource.name}()
384
+ \t${datasource.name}: () => DatasourceProvider.${datasource.name}
350
385
  }));`;
351
386
  await writeIfNotExists(datasourceProviderPath, datasourceProviderContent);
352
387
  await updateIndexTs(path.dirname(datasourceProviderPath));
@@ -357,7 +392,7 @@ export const DatasourceProvider = createBoundaryProvider('${contextName}Datasour
357
392
  await addToProvider(
358
393
  infraProvider.path,
359
394
  `${pascalName}Repository`,
360
- '../Http/Repositories',
395
+ '$' + context + '/Infrastructure/Http/Repositories',
361
396
  `DatasourceProvider.${datasource.name}`
362
397
  );
363
398
 
@@ -366,7 +401,7 @@ export const DatasourceProvider = createBoundaryProvider('${contextName}Datasour
366
401
  await addToProvider(
367
402
  appProvider.path,
368
403
  `${pascalName}Service`,
369
- '../Services',
404
+ '$' + context + '/Application/Services',
370
405
  `InfrastructureProvider.${pascalName}Repository`
371
406
  );
372
407
 
@@ -1,18 +1,27 @@
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 { selectContext, getContextPath, toPascalCase, getAvailableFiles, toCamelCase } from '../../utils/contextUtils.js';
4
+ import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
5
+ import { ensureProvider, addToProvider } from '../../utils/providerUtils.js';
5
6
 
6
- const datasourceTemplate = `import { BaseHttpDatasource } from '@azure-net/kit/infra';
7
- import { HttpService } from '@azure-net/kit/infra';
7
+ const datasourceWithResponseTemplate = `import { BaseHttpDatasource, type CreateRequestCallbackType } from '@azure-net/kit/infra';
8
+ import { {{responseClass}}, type {{responseInterface}} } from '{{responseImport}}';
9
+
10
+ export class {{name}}Datasource extends BaseHttpDatasource {
11
+ \tconstructor(private http: HttpService) {
12
+ \t\tsuper({ http });
13
+ \t}
14
+
15
+ \tasync createRequest<T>(callback: CreateRequestCallbackType<{{responseInterface}}<T>>) {
16
+ \t\treturn new {{responseClass}}<T>(await this.createRawRequest<{{responseInterface}}<T>>(callback));
17
+ \t}
18
+ }`;
19
+
20
+ const datasourceWithoutResponseTemplate = `import { BaseHttpDatasource } from '@azure-net/kit/infra';
8
21
 
9
22
  export class {{name}}Datasource extends BaseHttpDatasource {
10
- \tconstructor() {
11
- \t\tsuper({
12
- \t\t\thttp: new HttpService({
13
- \t\t\t\tbaseUrl: 'https://api.example.com'
14
- \t\t\t})
15
- \t\t});
23
+ \tconstructor(private http: HttpService) {
24
+ \t\tsuper({ http });
16
25
  \t}
17
26
  }`;
18
27
 
@@ -26,16 +35,121 @@ export default async function generateDatasource() {
26
35
  });
27
36
 
28
37
  const pascalName = toPascalCase(name);
38
+ const camelName = toCamelCase(name);
29
39
  const contextPath = getContextPath(context);
40
+
41
+ // Get available responses
42
+ const coreResponses = await getAvailableFiles(
43
+ path.join(process.cwd(), 'src/app/core/Response')
44
+ );
45
+
46
+ const contextResponses = context !== 'core'
47
+ ? await getAvailableFiles(path.join(contextPath, 'Infrastructure/Response'))
48
+ : [];
49
+
50
+ const allResponses = [
51
+ ...coreResponses.map(r => ({
52
+ title: `${r} (core)`,
53
+ value: { name: r, from: 'core' }
54
+ })),
55
+ ...contextResponses.map(r => ({
56
+ title: `${r} (${context})`,
57
+ value: { name: r, from: context }
58
+ }))
59
+ ];
60
+
61
+ if (allResponses.length === 0) {
62
+ console.error('āŒ No responses available. Create a response first with make:response');
63
+ return;
64
+ }
65
+
66
+ const { response } = await prompts({
67
+ type: 'select',
68
+ name: 'response',
69
+ message: 'Select response for datasource:',
70
+ choices: allResponses
71
+ });
72
+
73
+ // Determine interface name - typically IBackendApiDataSourceResponse or similar
74
+ const responseInterface = `I${response.name.replace('Response', '')}Response`;
75
+
30
76
  const datasourcePath = context === 'core'
31
77
  ? path.join(contextPath, 'Datasource')
32
78
  : path.join(contextPath, 'Infrastructure', 'Http', 'Datasource');
33
79
 
34
- const content = datasourceTemplate.replace(/{{name}}/g, pascalName);
35
- const filePath = path.join(datasourcePath, `${pascalName}Datasource.ts`);
80
+ const responseImport = response.from === 'core'
81
+ ? '$core/Response'
82
+ : context === 'core'
83
+ ? '../Response'
84
+ : '../../Response';
85
+
86
+ const content = datasourceWithResponseTemplate
87
+ .replace(/{{name}}/g, pascalName)
88
+ .replace(/{{responseClass}}/g, response.name)
89
+ .replace(/{{responseInterface}}/g, responseInterface)
90
+ .replace(/{{responseImport}}/g, responseImport);
36
91
 
92
+ const filePath = path.join(datasourcePath, `${pascalName}Datasource.ts`);
37
93
  await writeIfNotExists(filePath, content);
38
94
  await updateIndexTs(datasourcePath);
39
95
 
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
+
40
119
  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');
41
155
  }
@@ -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({
@@ -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`);
@@ -1,7 +1,7 @@
1
1
  import { writeIfNotExists } from '../../utils/fileUtils.js';
2
2
  import path from 'path';
3
3
 
4
- const schemaPath = path.join(process.cwd(), 'src/app/core/Schemas/Schema.ts');
4
+ const schemaPath = path.join(process.cwd(), 'src/app/core/Schema/Schema.ts');
5
5
 
6
6
  const schemaTemplate = `import { createSchemaFactory } from '@azure-net/kit';
7
7
  import { createRules, validationMessagesI18n } from '@azure-net/kit/schema';
@@ -11,7 +11,7 @@ export const Schema = createSchemaFactory(createRules(validationMessagesI18n));`
11
11
  export default async function initSchema() {
12
12
  await writeIfNotExists(schemaPath, schemaTemplate);
13
13
  await writeIfNotExists(
14
- path.join(process.cwd(), 'src/app/core/Schemas/index.ts'),
14
+ path.join(process.cwd(), 'src/app/core/Schema/index.ts'),
15
15
  `export * from './Schema';`
16
16
  );
17
17
 
@@ -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);
@@ -68,8 +68,13 @@ export const ${varName} = createBoundaryProvider(
68
68
  export async function addToProvider(providerPath, serviceName, importPath, dependency = null) {
69
69
  let content = await fs.readFile(providerPath, 'utf-8');
70
70
 
71
+ // Use alias import path instead of relative
72
+ const aliasImportPath = importPath.startsWith('$') ? importPath : importPath.startsWith('../')
73
+ ? importPath // Keep relative if it's internal to the same layer
74
+ : importPath;
75
+
71
76
  // Add import if not exists
72
- const importStatement = `import { ${serviceName} } from '${importPath}';`;
77
+ const importStatement = `import { ${serviceName} } from '${aliasImportPath}';`;
73
78
  if (!content.includes(serviceName)) {
74
79
  const lastImportIndex = content.lastIndexOf('import ');
75
80
  const endOfImportLine = content.indexOf('\n', lastImportIndex);