@azure-net/cli 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/azure-net.js CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  initMiddleware,
8
8
  initEdges,
9
9
  initTranslations,
10
- initSchema
10
+ initSchema,
11
+ initLint
11
12
  } from '../src/commands/init/index.js';
12
13
  import {
13
14
  generateDatasource,
@@ -28,6 +29,11 @@ program
28
29
  .version('2.0.0');
29
30
 
30
31
  // Init commands
32
+ program
33
+ .command('init:lint')
34
+ .description('Setup linting and commit tools')
35
+ .action(initLint)
36
+
31
37
  program
32
38
  .command('init:structure')
33
39
  .description('Generate base folder structure')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-net/cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "azure-net": "bin/azure-net.js"
@@ -2,74 +2,83 @@ import prompts from 'prompts';
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
4
  import { selectContext, getContextPath, toPascalCase, toCamelCase, getAvailableFiles } from '../../utils/contextUtils.js';
5
- import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
5
+ import { writeIfNotExists, updateIndexTs, ensureIndexExports } from '../../utils/fileUtils.js';
6
6
  import { ensureProvider, addToProvider } from '../../utils/providerUtils.js';
7
7
 
8
8
  // Templates for CRUD generation
9
9
  const entityTemplate = `export interface I{{name}} {
10
10
  \tid: number;
11
11
  \t// Add entity fields here
12
- \t[key: string]: unknown;
13
- }
14
-
15
- export type I{{name}}Collection = I{{name}}[];`;
12
+ \tcreated_at: string;
13
+ \tupdated_at: string;
14
+ }`;
16
15
 
17
- const requestTemplate = `import type { I{{name}} } from '../../Entities/{{name}}/index.js';
16
+ const portsIndexTemplate = `export interface I{{name}}Collection {
17
+ \tdata: I{{name}}[];
18
+ \ttotal: number;
19
+ }
18
20
 
19
- export type I{{name}}CreateRequest = Omit<I{{name}}, 'id'>;
20
- export type I{{name}}UpdateRequest = Partial<I{{name}}CreateRequest>;
21
21
  export interface I{{name}}CollectionQuery {
22
+ \tpage?: number;
23
+ \tperPage?: number;
22
24
  \t[key: string]: unknown;
25
+ }
26
+
27
+ export interface I{{name}}CreateRequest {
28
+ \t// Add request fields here
29
+ }
30
+
31
+ export interface I{{name}}UpdateRequest {
32
+ \t// Add request fields here
23
33
  }`;
24
34
 
25
35
  const repositoryTemplate = `import { {{datasource}} } from '{{datasourceImport}}';
26
- import type {
36
+ import type { I{{name}} } from '${{context}}/Domain/Entities/{{name}}';
37
+ import type {
27
38
  \tI{{name}}Collection,
28
- \tI{{name}},
39
+ \tI{{name}}CollectionQuery,
29
40
  \tI{{name}}CreateRequest,
30
- \tI{{name}}UpdateRequest,
31
- \tI{{name}}CollectionQuery
32
- } from '../../Domain/Entities/{{name}}/index.js';
33
- import type { I{{name}}CreateRequest as CreateRequest, I{{name}}UpdateRequest as UpdateRequest } from '../../Domain/Ports/{{name}}/index.js';
41
+ \tI{{name}}UpdateRequest
42
+ } from '${{context}}/Domain/Ports/{{name}}';
34
43
 
35
44
  export class {{name}}Repository {
36
45
  \tprivate endpoint = '{{endpoint}}';
37
46
  \t
38
47
  \tconstructor(private {{datasourceVar}}: {{datasource}}) {}
39
48
  \t
40
- \tpublic async collection(queryParams?: I{{name}}CollectionQuery) {
41
- \t\treturn await this.{{datasourceVar}}.createRequest<I{{name}}Collection>(({ http, query }) =>
42
- \t\t\thttp.get(\`\${this.endpoint}\${query.build(queryParams)}\`)
43
- \t\t);
49
+ \tpublic async collection(query?: I{{name}}CollectionQuery) {
50
+ \t\treturn this.{{datasourceVar}}.createRequest<I{{name}}Collection>(({ http, query: q }) =>
51
+ \t\t\thttp.get(\`\${this.endpoint}\${q.build(query || {})}\`)
52
+ \t\t).then(res => res.getData());
44
53
  \t}
45
54
  \t
46
55
  \tpublic async resource(id: number) {
47
- \t\treturn await this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
56
+ \t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
48
57
  \t\t\thttp.get(\`\${this.endpoint}/\${id}\`)
49
- \t\t);
58
+ \t\t).then(res => res.getData());
50
59
  \t}
51
60
  \t
52
- \tpublic async create(data: CreateRequest) {
53
- \t\treturn await this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
61
+ \tpublic async create(data: I{{name}}CreateRequest) {
62
+ \t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
54
63
  \t\t\thttp.post(this.endpoint, { json: data })
55
- \t\t);
64
+ \t\t).then(res => res.getData());
56
65
  \t}
57
66
  \t
58
- \tpublic async update(id: number, data: UpdateRequest) {
59
- \t\treturn await this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
67
+ \tpublic async update(id: number, data: I{{name}}UpdateRequest) {
68
+ \t\treturn this.{{datasourceVar}}.createRequest<I{{name}}>(({ http }) =>
60
69
  \t\t\thttp.put(\`\${this.endpoint}/\${id}\`, { json: data })
61
- \t\t);
70
+ \t\t).then(res => res.getData());
62
71
  \t}
63
72
  \t
64
73
  \tpublic async remove(id: number) {
65
- \t\treturn await this.{{datasourceVar}}.createRequest<never>(({ http }) =>
74
+ \t\treturn this.{{datasourceVar}}.createRequest<void>(({ http }) =>
66
75
  \t\t\thttp.delete(\`\${this.endpoint}/\${id}\`)
67
- \t\t);
76
+ \t\t).then(() => undefined);
68
77
  \t}
69
78
  }`;
70
79
 
71
80
  const serviceTemplate = `import { ClassMirror } from '@azure-net/kit';
72
- import { {{name}}Repository } from '../../Infrastructure/Repositories/index.js';
81
+ import { {{name}}Repository } from '../../Infrastructure/Http/Repositories';
73
82
 
74
83
  export class {{name}}Service extends ClassMirror<{{name}}Repository> {
75
84
  \tconstructor(private {{camelName}}Repository: {{name}}Repository) {
@@ -84,7 +93,7 @@ export class {{name}}Service extends ClassMirror<{{name}}Repository> {
84
93
  }`;
85
94
 
86
95
  const createSchemaTemplate = `import { {{schemaFactory}} } from '{{schemaImport}}';
87
- import type { I{{name}}CreateRequest } from '../../Domain/Ports/{{name}}/index.js';
96
+ import type { I{{name}}CreateRequest } from '${{context}}/Domain/Ports/{{name}}';
88
97
 
89
98
  export const Create{{name}}Schema = {{schemaFactory}}<I{{name}}CreateRequest>()
90
99
  \t.rules((rules) => ({
@@ -93,7 +102,7 @@ export const Create{{name}}Schema = {{schemaFactory}}<I{{name}}CreateRequest>()
93
102
  \t.create();`;
94
103
 
95
104
  const updateSchemaTemplate = `import { {{schemaFactory}} } from '{{schemaImport}}';
96
- import type { I{{name}}UpdateRequest } from '../../Domain/Ports/{{name}}/index.js';
105
+ import type { I{{name}}UpdateRequest } from '${{context}}/Domain/Ports/{{name}}';
97
106
 
98
107
  export const Update{{name}}Schema = {{schemaFactory}}<I{{name}}UpdateRequest>()
99
108
  \t.rules((rules) => ({
@@ -102,15 +111,19 @@ export const Update{{name}}Schema = {{schemaFactory}}<I{{name}}UpdateRequest>()
102
111
  \t.create();`;
103
112
 
104
113
  const presenterTemplate = `import { {{presenterFactory}} } from '{{presenterImport}}';
105
- import { ApplicationProvider } from '../../Application/index.js';
106
- import { Create{{name}}Schema, Update{{name}}Schema } from '../Schemas/index.js';
107
- import type { I{{name}}CreateRequest, I{{name}}UpdateRequest, I{{name}}CollectionQuery } from '../../Domain/Ports/{{name}}/index.js';
114
+ import { ApplicationProvider } from '${{context}}/Application';
115
+ import { Create{{name}}Schema, Update{{name}}Schema } from './Schema';
116
+ import type {
117
+ \tI{{name}}CollectionQuery,
118
+ \tI{{name}}CreateRequest,
119
+ \tI{{name}}UpdateRequest
120
+ } from '${{context}}/Domain/Ports/{{name}}';
108
121
 
109
- export const {{contextName}}{{name}}Presenter = {{presenterFactory}}('{{contextName}}{{name}}Presenter', ({ createAsyncResource, createAsyncAction }) => {
122
+ export const {{name}}Presenter = {{presenterFactory}}('{{name}}Presenter', ({ createAsyncResource, createAsyncAction }) => {
110
123
  \tconst { {{name}}Service } = ApplicationProvider();
111
124
  \t
112
- \tconst collection = async (queryParams?: I{{name}}CollectionQuery) =>
113
- \t\tawait createAsyncResource({{name}}Service.collection(queryParams));
125
+ \tconst collection = async (query?: I{{name}}CollectionQuery) =>
126
+ \t\tawait createAsyncResource({{name}}Service.collection(query));
114
127
  \t
115
128
  \tconst resource = async (id: number) =>
116
129
  \t\tawait createAsyncResource({{name}}Service.resource(id));
@@ -150,15 +163,15 @@ export default async function generateCrud() {
150
163
  // Get available datasources
151
164
  const contextPath = getContextPath(context);
152
165
  const coreDatasources = await getAvailableFiles(
153
- path.join(process.cwd(), 'src/app/core/Datasources')
166
+ path.join(process.cwd(), 'src/app/core/Datasource')
154
167
  );
155
168
  const contextDatasources = await getAvailableFiles(
156
- path.join(contextPath, 'Infrastructure', 'Datasources')
169
+ path.join(contextPath, 'Infrastructure/Http/Datasource')
157
170
  );
158
171
 
159
172
  const allDatasources = [
160
- ...coreDatasources.map(d => ({ title: `${d}Datasource (core)`, value: { name: `${d}Datasource`, from: 'core' } })),
161
- ...contextDatasources.map(d => ({ title: `${d}Datasource (${context})`, value: { name: `${d}Datasource`, from: context } }))
173
+ ...coreDatasources.map(d => ({ title: `${d} (core)`, value: { name: d, from: 'core' } })),
174
+ ...contextDatasources.map(d => ({ title: `${d} (${context})`, value: { name: d, from: context } }))
162
175
  ];
163
176
 
164
177
  const { datasource } = await prompts({
@@ -170,7 +183,7 @@ export default async function generateCrud() {
170
183
 
171
184
  // Get schema and presenter factories
172
185
  const coreSchemas = await getAvailableFiles(
173
- path.join(process.cwd(), 'src/app/core/Schemas')
186
+ path.join(process.cwd(), 'src/app/core/Schema')
174
187
  );
175
188
 
176
189
  const { schemaType } = await prompts({
@@ -178,13 +191,13 @@ export default async function generateCrud() {
178
191
  name: 'schemaType',
179
192
  message: 'Select schema factory:',
180
193
  choices: [
181
- { title: 'Use schema from package', value: 'package' },
194
+ { title: 'Use createSchemaFactory from package', value: 'package' },
182
195
  ...coreSchemas.map(s => ({ title: `Use ${s} from core`, value: s }))
183
196
  ]
184
197
  });
185
198
 
186
199
  const corePresenters = await getAvailableFiles(
187
- path.join(process.cwd(), 'src/app/core/Presenters')
200
+ path.join(process.cwd(), 'src/app/core/Presenter')
188
201
  );
189
202
 
190
203
  const { presenterType } = await prompts({
@@ -210,23 +223,26 @@ export default async function generateCrud() {
210
223
  entityTemplate.replace(/{{name}}/g, pascalName)
211
224
  );
212
225
 
213
- // 2. Create Ports (Requests/Responses)
226
+ // 2. Create Ports
214
227
  const portsPath = path.join(contextPath, 'Domain', 'Ports', pascalName);
215
228
  await writeIfNotExists(
216
229
  path.join(portsPath, 'index.ts'),
217
- requestTemplate.replace(/{{name}}/g, pascalName)
230
+ portsIndexTemplate
231
+ .replace(/{{name}}/g, pascalName)
232
+ .replace(/{{context}}/g, context)
218
233
  );
219
234
 
220
235
  // 3. Create Repository
221
- const repoPath = path.join(contextPath, 'Infrastructure', 'Repositories');
236
+ const repoPath = path.join(contextPath, 'Infrastructure', 'Http', 'Repositories');
222
237
  const datasourceImport = datasource.from === 'core'
223
- ? '$core/Datasources/index.js'
224
- : '../Datasources/index.js';
238
+ ? '$core/Datasource'
239
+ : `$${context}/Infrastructure/Http/Datasource`;
225
240
 
226
241
  await writeIfNotExists(
227
242
  path.join(repoPath, `${pascalName}Repository.ts`),
228
243
  repositoryTemplate
229
244
  .replace(/{{name}}/g, pascalName)
245
+ .replace(/{{context}}/g, context)
230
246
  .replace(/{{endpoint}}/g, endpoint)
231
247
  .replace(/{{datasource}}/g, datasource.name)
232
248
  .replace(/{{datasourceImport}}/g, datasourceImport)
@@ -244,17 +260,21 @@ export default async function generateCrud() {
244
260
  );
245
261
  await updateIndexTs(servicePath);
246
262
 
247
- // 5. Create Schemas
248
- const schemaPath = path.join(contextPath, 'Delivery', 'Schemas');
249
- const schemaFactory = schemaType === 'package' ? 'schema' : schemaType;
263
+ // 5. Create Delivery module structure
264
+ const deliveryModulePath = path.join(contextPath, 'Delivery', pascalName);
265
+ const schemaPath = path.join(deliveryModulePath, 'Schema');
266
+
267
+ // Create schemas
268
+ const schemaFactory = schemaType === 'package' ? 'createSchemaFactory' : schemaType;
250
269
  const schemaImport = schemaType === 'package'
251
270
  ? '@azure-net/kit'
252
- : '$core/Schemas/index.js';
271
+ : '$core/Schema';
253
272
 
254
273
  await writeIfNotExists(
255
274
  path.join(schemaPath, `Create${pascalName}Schema.ts`),
256
275
  createSchemaTemplate
257
276
  .replace(/{{name}}/g, pascalName)
277
+ .replace(/{{context}}/g, context)
258
278
  .replace(/{{schemaFactory}}/g, schemaFactory)
259
279
  .replace(/{{schemaImport}}/g, schemaImport)
260
280
  );
@@ -263,61 +283,68 @@ export default async function generateCrud() {
263
283
  path.join(schemaPath, `Update${pascalName}Schema.ts`),
264
284
  updateSchemaTemplate
265
285
  .replace(/{{name}}/g, pascalName)
286
+ .replace(/{{context}}/g, context)
266
287
  .replace(/{{schemaFactory}}/g, schemaFactory)
267
288
  .replace(/{{schemaImport}}/g, schemaImport)
268
289
  );
269
290
  await updateIndexTs(schemaPath);
270
291
 
271
292
  // 6. Create Presenter
272
- const presenterPath = path.join(contextPath, 'Delivery', 'Presenters');
273
293
  const presenterFactory = presenterType === 'package' ? 'createPresenter' : presenterType;
274
294
  const presenterImport = presenterType === 'package'
275
295
  ? '@azure-net/kit'
276
- : '$core/Presenters/index.js';
296
+ : '$core/Presenter';
277
297
 
278
298
  await writeIfNotExists(
279
- path.join(presenterPath, `${pascalName}Presenter.ts`),
299
+ path.join(deliveryModulePath, `${pascalName}Presenter.ts`),
280
300
  presenterTemplate
281
301
  .replace(/{{name}}/g, pascalName)
282
- .replace(/{{contextName}}/g, contextName)
302
+ .replace(/{{context}}/g, context)
283
303
  .replace(/{{presenterFactory}}/g, presenterFactory)
284
304
  .replace(/{{presenterImport}}/g, presenterImport)
285
305
  );
286
- await updateIndexTs(presenterPath);
306
+
307
+ // Create module index
308
+ await writeIfNotExists(
309
+ path.join(deliveryModulePath, 'index.ts'),
310
+ `export * from './${pascalName}Presenter';\nexport * from './Schema';`
311
+ );
287
312
 
288
313
  // 7. Setup Providers
289
314
  // Ensure DatasourceProvider
290
- await ensureProvider(context, 'Infrastructure', { datasource: datasource.name });
315
+ const datasourceProviderPath = path.join(contextPath, 'Infrastructure', 'Providers', 'DatasourceProvider.ts');
316
+ if (!(await fs.access(datasourceProviderPath).then(() => true).catch(() => false))) {
317
+ const datasourceProviderContent = `import { createBoundaryProvider } from '@azure-net/kit';
318
+ import { ${datasource.name} } from '${datasource.from === 'core' ? '$core/Datasource' : '../Http/Datasource'}';
319
+
320
+ export const DatasourceProvider = createBoundaryProvider('${contextName}DatasourceProvider', () => ({
321
+ \t${datasource.name}: () => new ${datasource.name}()
322
+ }));`;
323
+ await writeIfNotExists(datasourceProviderPath, datasourceProviderContent);
324
+ await updateIndexTs(path.dirname(datasourceProviderPath));
325
+ }
291
326
 
292
- // Ensure InfrastructureProvider and add repository
327
+ // Ensure InfrastructureProvider
293
328
  const infraProvider = await ensureProvider(context, 'Infrastructure', {});
294
329
  await addToProvider(
295
330
  infraProvider.path,
296
331
  `${pascalName}Repository`,
297
- '../Repositories/index.js'
332
+ '../Http/Repositories',
333
+ `DatasourceProvider.${datasource.name}`
298
334
  );
299
335
 
300
- // Update provider with datasource dependency
301
- let infraContent = await fs.readFile(infraProvider.path, 'utf-8');
302
- const repoFactory = `${pascalName}Repository: () => new ${pascalName}Repository()`;
303
- const repoWithDatasource = `${pascalName}Repository: () => new ${pascalName}Repository(DatasourceProvider.${datasource.name})`;
304
- infraContent = infraContent.replace(repoFactory, repoWithDatasource);
305
- await fs.writeFile(infraProvider.path, infraContent, 'utf-8');
306
-
307
- // Ensure ApplicationProvider and add service
336
+ // Ensure ApplicationProvider
308
337
  const appProvider = await ensureProvider(context, 'Application', { infrastructure: true });
309
338
  await addToProvider(
310
339
  appProvider.path,
311
340
  `${pascalName}Service`,
312
- '../Services/index.js'
341
+ '../Services',
342
+ `InfrastructureProvider.${pascalName}Repository`
313
343
  );
314
344
 
315
- // Update provider with repository dependency
316
- let appContent = await fs.readFile(appProvider.path, 'utf-8');
317
- const serviceFactory = `${pascalName}Service: () => new ${pascalName}Service()`;
318
- const serviceWithRepo = `${pascalName}Service: () => new ${pascalName}Service(InfrastructureProvider.${pascalName}Repository)`;
319
- appContent = appContent.replace(serviceFactory, serviceWithRepo);
320
- await fs.writeFile(appProvider.path, appContent, 'utf-8');
345
+ // Update all layer indexes
346
+ await ensureIndexExports(path.join(contextPath, 'Infrastructure'));
347
+ await ensureIndexExports(path.join(contextPath, 'Application'));
321
348
 
322
349
  console.log(`✅ CRUD module for ${pascalName} created successfully!`);
323
350
  }
@@ -3,11 +3,16 @@ import { selectContext, getContextPath, toPascalCase } from '../../utils/context
3
3
  import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
4
4
  import path from 'path';
5
5
 
6
- const datasourceTemplate = `import { BaseHttpDatasource, HttpService } from '@azure-net/kit';
6
+ const datasourceTemplate = `import { BaseHttpDatasource } from '@azure-net/kit/infra';
7
+ import { HttpService } from '@azure-net/kit/infra';
7
8
 
8
9
  export class {{name}}Datasource extends BaseHttpDatasource {
9
10
  \tconstructor() {
10
- \t\tsuper({ http: new HttpService({ baseUrl: '' }) });
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});
11
16
  \t}
12
17
  }`;
13
18
 
@@ -17,14 +22,14 @@ export default async function generateDatasource() {
17
22
  const { name } = await prompts({
18
23
  type: 'text',
19
24
  name: 'name',
20
- message: 'Datasource name (PascalCase):'
25
+ message: 'Datasource name (without "Datasource" suffix):'
21
26
  });
22
27
 
23
28
  const pascalName = toPascalCase(name);
24
29
  const contextPath = getContextPath(context);
25
30
  const datasourcePath = context === 'core'
26
- ? path.join(contextPath, 'Datasources')
27
- : path.join(contextPath, 'Infrastructure', 'Datasources');
31
+ ? path.join(contextPath, 'Datasource')
32
+ : path.join(contextPath, 'Infrastructure', 'Http', 'Datasource');
28
33
 
29
34
  const content = datasourceTemplate.replace(/{{name}}/g, pascalName);
30
35
  const filePath = path.join(datasourcePath, `${pascalName}Datasource.ts`);
@@ -3,15 +3,15 @@ import path from 'path';
3
3
  import { selectContext, getContextPath, toPascalCase, getAvailableFiles } from '../../utils/contextUtils.js';
4
4
  import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
5
5
 
6
- const presenterWithCoreTemplate = `import { {{corePresenter}} } from '$core/Presenters/index.js';
6
+ const presenterWithCoreTemplate = `import { {{corePresenter}} } from '$core/Presenter';
7
7
  {{serviceImport}}
8
8
 
9
- export const {{contextName}}{{name}}Presenter = {{corePresenter}}('{{contextName}}{{name}}Presenter', ({ createAsyncResource, createAsyncAction }) => {
9
+ export const {{name}}Presenter = {{corePresenter}}('{{name}}Presenter', ({ createAsyncResource, createAsyncAction }) => {
10
10
  \t{{serviceInit}}
11
11
  \t
12
12
  \tconst example = async () => {
13
13
  \t\t// Implement presenter logic
14
- \t\treturn {};
14
+ \t\treturn await createAsyncResource(Promise.resolve({}));
15
15
  \t};
16
16
  \t
17
17
  \treturn { example };
@@ -20,7 +20,7 @@ export const {{contextName}}{{name}}Presenter = {{corePresenter}}('{{contextName
20
20
  const presenterWithPackageTemplate = `import { createPresenter } from '@azure-net/kit';
21
21
  {{serviceImport}}
22
22
 
23
- export const {{contextName}}{{name}}Presenter = createPresenter('{{contextName}}{{name}}Presenter', () => {
23
+ export const {{name}}Presenter = createPresenter('{{name}}Presenter', () => {
24
24
  \t{{serviceInit}}
25
25
  \t
26
26
  \tconst example = async () => {
@@ -42,12 +42,12 @@ export default async function generatePresenter() {
42
42
  const { name } = await prompts({
43
43
  type: 'text',
44
44
  name: 'name',
45
- message: 'Presenter name (without "Presenter" suffix):'
45
+ message: 'Module name (will create folder in Delivery):'
46
46
  });
47
47
 
48
48
  // Check for core presenters
49
49
  const corePresenters = await getAvailableFiles(
50
- path.join(process.cwd(), 'src/app/core/Presenters')
50
+ path.join(process.cwd(), 'src/app/core/Presenter')
51
51
  );
52
52
 
53
53
  const presenterChoices = [
@@ -70,7 +70,7 @@ export default async function generatePresenter() {
70
70
 
71
71
  const serviceChoices = [
72
72
  { title: 'Without service', value: null },
73
- ...services.map(s => ({ title: `${s}Service`, value: `${s}Service` }))
73
+ ...services.map(s => ({ title: `${s}`, value: s }))
74
74
  ];
75
75
 
76
76
  const { service } = await prompts({
@@ -81,12 +81,11 @@ export default async function generatePresenter() {
81
81
  });
82
82
 
83
83
  const pascalName = toPascalCase(name);
84
- const contextName = toPascalCase(context);
85
- const presenterPath = path.join(contextPath, 'Delivery', 'Presenters');
86
- const filePath = path.join(presenterPath, `${pascalName}Presenter.ts`);
84
+ const modulePath = path.join(contextPath, 'Delivery', pascalName);
85
+ const filePath = path.join(modulePath, `${pascalName}Presenter.ts`);
87
86
 
88
87
  const serviceImport = service
89
- ? `import { ApplicationProvider } from '../../Application/index.js';`
88
+ ? `import { ApplicationProvider } from '$${context}/Application';`
90
89
  : '';
91
90
 
92
91
  const serviceInit = service
@@ -95,19 +94,20 @@ export default async function generatePresenter() {
95
94
 
96
95
  const content = presenterType === 'package'
97
96
  ? presenterWithPackageTemplate
98
- .replace(/{{contextName}}/g, contextName)
99
97
  .replace(/{{name}}/g, pascalName)
100
98
  .replace(/{{serviceImport}}/g, serviceImport)
101
99
  .replace(/{{serviceInit}}/g, serviceInit)
102
100
  : presenterWithCoreTemplate
103
- .replace(/{{contextName}}/g, contextName)
104
101
  .replace(/{{name}}/g, pascalName)
105
102
  .replace(/{{corePresenter}}/g, presenterType)
106
103
  .replace(/{{serviceImport}}/g, serviceImport)
107
104
  .replace(/{{serviceInit}}/g, serviceInit);
108
105
 
109
106
  await writeIfNotExists(filePath, content);
110
- await updateIndexTs(presenterPath);
107
+ await writeIfNotExists(
108
+ path.join(modulePath, 'index.ts'),
109
+ `export * from './${pascalName}Presenter';`
110
+ );
111
111
 
112
112
  console.log(`✅ Presenter created at ${filePath}`);
113
113
  }
@@ -1,7 +1,7 @@
1
1
  import prompts from 'prompts';
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
- import { selectContext, getContextPath, toPascalCase, getAvailableFiles } from '../../utils/contextUtils.js';
4
+ import { selectContext, getContextPath, toPascalCase, getAvailableFiles, toCamelCase } from '../../utils/contextUtils.js';
5
5
  import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
6
6
  import { ensureProvider, addToProvider } from '../../utils/providerUtils.js';
7
7
 
@@ -34,15 +34,15 @@ export default async function generateRepository() {
34
34
  // Get available datasources
35
35
  const contextPath = getContextPath(context);
36
36
  const coreDatasources = await getAvailableFiles(
37
- path.join(process.cwd(), 'src/app/core/Datasources')
37
+ path.join(process.cwd(), 'src/app/core/Datasource')
38
38
  );
39
39
  const contextDatasources = await getAvailableFiles(
40
- path.join(contextPath, 'Infrastructure', 'Datasources')
40
+ path.join(contextPath, 'Infrastructure', 'Http', 'Datasource')
41
41
  );
42
42
 
43
43
  const allDatasources = [
44
- ...coreDatasources.map(d => ({ title: `${d}Datasource (core)`, value: { name: `${d}Datasource`, from: 'core' } })),
45
- ...contextDatasources.map(d => ({ title: `${d}Datasource (${context})`, value: { name: `${d}Datasource`, from: context } }))
44
+ ...coreDatasources.map(d => ({ title: `${d} (core)`, value: { name: d, from: 'core' } })),
45
+ ...contextDatasources.map(d => ({ title: `${d} (${context})`, value: { name: d, from: context } }))
46
46
  ];
47
47
 
48
48
  if (allDatasources.length === 0) {
@@ -58,40 +58,45 @@ export default async function generateRepository() {
58
58
  });
59
59
 
60
60
  const pascalName = toPascalCase(name);
61
- const repoPath = path.join(contextPath, 'Infrastructure', 'Repositories');
61
+ const contextName = toPascalCase(context);
62
+ const repoPath = path.join(contextPath, 'Infrastructure', 'Http', 'Repositories');
62
63
  const filePath = path.join(repoPath, `${pascalName}Repository.ts`);
63
64
 
64
65
  // Generate repository
65
66
  const datasourceImport = datasource.from === 'core'
66
- ? '$core/Datasources/index.js'
67
- : '../Datasources/index.js';
67
+ ? '$core/Datasource'
68
+ : '../Datasource';
68
69
 
69
70
  const content = repositoryTemplate
70
71
  .replace(/{{name}}/g, pascalName)
71
72
  .replace(/{{datasource}}/g, datasource.name)
72
73
  .replace(/{{datasourceImport}}/g, datasourceImport)
73
- .replace(/{{datasourceVar}}/g, toPascalCase(datasource.name).charAt(0).toLowerCase() + toPascalCase(datasource.name).slice(1));
74
+ .replace(/{{datasourceVar}}/g, toCamelCase(datasource.name));
74
75
 
75
76
  await writeIfNotExists(filePath, content);
76
77
  await updateIndexTs(repoPath);
77
78
 
78
79
  // Ensure DatasourceProvider exists
79
- const datasourceProvider = await ensureProvider(context, 'Infrastructure', { datasource: datasource.name });
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
91
 
81
- // Ensure InfrastructureProvider exists and add repository
82
- const infraProvider = await ensureProvider(context, 'Infrastructure', { datasource: datasource.name });
92
+ // Ensure InfrastructureProvider and add repository
93
+ const infraProvider = await ensureProvider(context, 'Infrastructure', { hasDatasource: true });
83
94
  await addToProvider(
84
95
  infraProvider.path,
85
96
  `${pascalName}Repository`,
86
- '../Repositories/index.js'
97
+ '../Http/Repositories',
98
+ `DatasourceProvider.${datasource.name}`
87
99
  );
88
100
 
89
- // Add datasource dependency to repository in provider
90
- let providerContent = await fs.readFile(infraProvider.path, 'utf-8');
91
- const repoFactory = `${pascalName}Repository: () => new ${pascalName}Repository()`;
92
- const repoWithDatasource = `${pascalName}Repository: () => new ${pascalName}Repository(DatasourceProvider.${datasource.name})`;
93
- providerContent = providerContent.replace(repoFactory, repoWithDatasource);
94
- await fs.writeFile(infraProvider.path, providerContent, 'utf-8');
95
-
96
101
  console.log(`✅ Repository created at ${filePath}`);
97
102
  }
@@ -3,8 +3,8 @@ import path from 'path';
3
3
  import { selectContext, getContextPath, toPascalCase, getAvailableFiles } from '../../utils/contextUtils.js';
4
4
  import { writeIfNotExists, updateIndexTs } from '../../utils/fileUtils.js';
5
5
 
6
- const schemaWithCoreTemplate = `import { {{coreSchema}} } from '$core/Schemas/index.js';
7
- import type { I{{name}}Request } from '../../Domain/Ports/{{name}}/index.js';
6
+ const schemaWithCoreTemplate = `import { {{coreSchema}} } from '$core/Schema';
7
+ import type { I{{name}}Request } from '${{context}}/Domain/Ports/{{entity}}';
8
8
 
9
9
  export const {{name}}Schema = {{coreSchema}}<I{{name}}Request>()
10
10
  \t.rules((rules) => ({
@@ -12,10 +12,13 @@ export const {{name}}Schema = {{coreSchema}}<I{{name}}Request>()
12
12
  \t}))
13
13
  \t.create();`;
14
14
 
15
- const schemaWithPackageTemplate = `import { schema } from '@azure-net/kit';
16
- import type { I{{name}}Request } from '../../Domain/Ports/{{name}}/index.js';
15
+ const schemaWithPackageTemplate = `import { createSchemaFactory } from '@azure-net/kit';
16
+ import { createRules, validationMessagesI18n } from '@azure-net/kit/schema';
17
+ import type { I{{name}}Request } from '${{context}}/Domain/Ports/{{entity}}';
17
18
 
18
- export const {{name}}Schema = schema<I{{name}}Request>()
19
+ const Schema = createSchemaFactory(createRules(validationMessagesI18n));
20
+
21
+ export const {{name}}Schema = Schema<I{{name}}Request>()
19
22
  \t.rules((rules) => ({
20
23
  \t\t// Add validation rules here
21
24
  \t}))
@@ -29,6 +32,12 @@ export default async function generateSchema() {
29
32
  return;
30
33
  }
31
34
 
35
+ const { module } = await prompts({
36
+ type: 'text',
37
+ name: 'module',
38
+ message: 'Module name (folder in Delivery):'
39
+ });
40
+
32
41
  const { name } = await prompts({
33
42
  type: 'text',
34
43
  name: 'name',
@@ -37,11 +46,11 @@ export default async function generateSchema() {
37
46
 
38
47
  // Check for core schemas
39
48
  const coreSchemas = await getAvailableFiles(
40
- path.join(process.cwd(), 'src/app/core/Schemas')
49
+ path.join(process.cwd(), 'src/app/core/Schema')
41
50
  );
42
51
 
43
52
  const choices = [
44
- { title: 'Use schema from package', value: 'package' },
53
+ { title: 'Use createSchemaFactory from package', value: 'package' },
45
54
  ...coreSchemas.map(s => ({ title: `Use ${s} from core`, value: s }))
46
55
  ];
47
56
 
@@ -53,18 +62,34 @@ export default async function generateSchema() {
53
62
  });
54
63
 
55
64
  const pascalName = toPascalCase(name);
65
+ const moduleName = toPascalCase(module);
56
66
  const contextPath = getContextPath(context);
57
- const schemaPath = path.join(contextPath, 'Delivery', 'Schemas');
67
+ const schemaPath = path.join(contextPath, 'Delivery', moduleName, 'Schema');
58
68
  const filePath = path.join(schemaPath, `${pascalName}Schema.ts`);
59
69
 
60
70
  const content = schemaType === 'package'
61
- ? schemaWithPackageTemplate.replace(/{{name}}/g, pascalName)
71
+ ? schemaWithPackageTemplate
72
+ .replace(/{{name}}/g, pascalName)
73
+ .replace(/{{context}}/g, context)
74
+ .replace(/{{entity}}/g, moduleName)
62
75
  : schemaWithCoreTemplate
63
76
  .replace(/{{name}}/g, pascalName)
77
+ .replace(/{{context}}/g, context)
78
+ .replace(/{{entity}}/g, moduleName)
64
79
  .replace(/{{coreSchema}}/g, schemaType);
65
80
 
66
81
  await writeIfNotExists(filePath, content);
67
82
  await updateIndexTs(schemaPath);
68
83
 
84
+ // Update module index
85
+ const moduleIndexPath = path.join(contextPath, 'Delivery', moduleName, 'index.ts');
86
+ if (await fs.access(moduleIndexPath).then(() => true).catch(() => false)) {
87
+ let indexContent = await fs.readFile(moduleIndexPath, 'utf-8');
88
+ if (!indexContent.includes(`export * from './Schema'`)) {
89
+ indexContent += `\nexport * from './Schema';`;
90
+ await fs.writeFile(moduleIndexPath, indexContent, 'utf-8');
91
+ }
92
+ }
93
+
69
94
  console.log(`✅ Schema created at ${filePath}`);
70
95
  }
@@ -3,4 +3,5 @@ export { default as initAliases } from './initAliases.js';
3
3
  export { default as initMiddleware } from './initMiddleware.js';
4
4
  export { default as initEdges } from './initEdges.js';
5
5
  export { default as initTranslations } from './initTranslations.js';
6
- export { default as initSchema } from './initSchema.js';
6
+ export { default as initSchema } from './initSchema.js';
7
+ export { default as initLint } from './initLint.js';
@@ -0,0 +1,138 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { execSync } from 'child_process';
4
+ import prompts from 'prompts';
5
+
6
+ const dependencies = [
7
+ '@commitlint/cli',
8
+ 'semantic-release',
9
+ '@semantic-release/git',
10
+ 'git-cz',
11
+ 'husky',
12
+ 'commitizen',
13
+ 'env-cmd'
14
+ ];
15
+
16
+ const scriptsToAdd = {
17
+ precommit: 'pnpm lint && pnpm check',
18
+ 'semantic-release': 'env-cmd semantic-release',
19
+ commit: 'pnpm format && git add . && git-cz && git push',
20
+ release: 'pnpm commit && pnpm semantic-release'
21
+ };
22
+
23
+ const commitlintConfig = `export default {
24
+ rules: {
25
+ 'body-leading-blank': [2, 'always'],
26
+ 'footer-leading-blank': [2, 'always'],
27
+ 'header-max-length': [2, 'always', 72],
28
+ 'subject-empty': [2, 'never'],
29
+ 'subject-full-stop': [2, 'never', '.'],
30
+ 'type-case': [2, 'always', 'lower-case'],
31
+ 'type-empty': [2, 'never'],
32
+ 'type-enum': [2, 'always', ['feat', 'fix', 'refactor', 'perf', 'chore', 'docs', 'style', 'test', 'ci']]
33
+ }
34
+ };
35
+ `;
36
+
37
+ const releaseConfig = `export default {
38
+ branches: ['main', 'dev'],
39
+ dryRun: false,
40
+ ci: false,
41
+ plugins: [
42
+ [
43
+ '@semantic-release/commit-analyzer',
44
+ {
45
+ releaseRules: [
46
+ { type: 'release', release: 'major' },
47
+ { type: 'feature', release: 'minor' },
48
+ { type: 'perf', release: false },
49
+ { type: 'refactor', release: false },
50
+ { type: 'fix', release: 'patch' },
51
+ { type: 'chore', release: false },
52
+ { type: 'ci', release: false },
53
+ { type: 'docs', scope: 'README', release: false },
54
+ { type: 'test', release: false },
55
+ { type: 'style', release: false }
56
+ ]
57
+ }
58
+ ],
59
+ '@semantic-release/release-notes-generator'
60
+ ]
61
+ };
62
+ `;
63
+
64
+ const prettierConfig = `{
65
+ "useTabs": true,
66
+ "singleQuote": true,
67
+ "trailingComma": "none",
68
+ "tabWidth": 2,
69
+ "semi": true,
70
+ "printWidth": 150,
71
+ "plugins": ["prettier-plugin-svelte"],
72
+ "overrides": [
73
+ {
74
+ "files": "*.svelte",
75
+ "options": {
76
+ "parser": "svelte"
77
+ }
78
+ }
79
+ ]
80
+ }
81
+ `;
82
+
83
+ export default async function initLint() {
84
+ const cwd = process.cwd();
85
+ const pkgPath = path.join(cwd, 'package.json');
86
+
87
+ if (!fs.existsSync(pkgPath)) {
88
+ console.error('❌ package.json not found in this directory');
89
+ process.exit(1);
90
+ }
91
+
92
+ const { manager } = await prompts({
93
+ type: 'select',
94
+ name: 'manager',
95
+ message: 'Choose your package manager:',
96
+ choices: [
97
+ { title: 'pnpm', value: 'pnpm' },
98
+ { title: 'npm', value: 'npm' },
99
+ { title: 'yarn', value: 'yarn' }
100
+ ],
101
+ initial: 0
102
+ });
103
+
104
+ const installCommand =
105
+ manager === 'pnpm'
106
+ ? `pnpm add -D ${dependencies.join(' ')}`
107
+ : manager === 'npm'
108
+ ? `npm install -D ${dependencies.join(' ')}`
109
+ : `yarn add -D ${dependencies.join(' ')}`;
110
+
111
+ console.log(`📦 Installing packages with ${manager}...`);
112
+ execSync(installCommand, { stdio: 'inherit' });
113
+
114
+ console.log('📁 Updating package.json...');
115
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
116
+
117
+ pkg.scripts = {
118
+ ...pkg.scripts,
119
+ ...scriptsToAdd
120
+ };
121
+
122
+ pkg.config = pkg.config || {};
123
+ pkg.config.commitizen = {
124
+ path: 'git-cz'
125
+ };
126
+
127
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4));
128
+
129
+ // Write config files
130
+ fs.writeFileSync(path.join(cwd, 'commitlint.config.ts'), commitlintConfig);
131
+ fs.writeFileSync(path.join(cwd, 'release.config.ts'), releaseConfig);
132
+ fs.writeFileSync(path.join(cwd, '.prettierrc'), prettierConfig);
133
+
134
+ console.log('🔧 Initializing Husky...');
135
+ execSync(`${manager === 'npm' ? 'npx' : manager} husky`, { stdio: 'inherit' });
136
+ execSync('npm run format', { stdio: 'inherit' });
137
+ console.log('✅ Linting and release setup completed! Change your prepare script in package.json: node -e \"if (process.env.NODE_ENV !== \'production\'){process.exit(1)} \" || (husky && svelte-kit sync)');
138
+ }
@@ -5,37 +5,22 @@ const middlewarePath = path.join(process.cwd(), 'src/app/core/Middleware/Middlew
5
5
  const hooksServerPath = path.join(process.cwd(), 'src/hooks.server.ts');
6
6
  const layoutPath = path.join(process.cwd(), 'src/routes/+layout.svelte');
7
7
 
8
- const middlewareTemplate = `import { createMiddlewareManager, type IMiddleware } from '@azure-net/kit';
9
-
10
- const AuthMiddleware: IMiddleware = async ({ next, page, event, isServer, cookies }) => {
11
- \tconst user = isServer ? event?.locals?.user : page?.data?.user;
12
- \tif (isServer && !user && cookies.get('token') && event) {
13
- \t\t// Add auth logic here
14
- \t}
15
- \tnext();
16
- };
17
-
18
- const GuardMiddleware: IMiddleware = async ({ next, page, event, isServer, to }) => {
19
- \tconst user = isServer ? event?.locals?.user : page?.data?.user;
20
- \tif (!user && !to.pathname.startsWith('/login')) {
21
- \t\tnext('/login');
22
- \t}
23
- \tif (user && to.pathname.startsWith('/login')) {
24
- \t\tnext('/');
25
- \t}
26
- \tnext();
27
- };
28
-
29
- export const { clientMiddleware, serverMiddleware } = createMiddlewareManager([AuthMiddleware, GuardMiddleware]);`;
8
+ const middlewareTemplate = `import { createMiddlewareManager } from '@azure-net/kit';
9
+
10
+ export const { clientMiddleware, serverMiddleware } = createMiddlewareManager([]);`;
30
11
 
31
12
  export default async function initMiddleware() {
32
13
  await writeIfNotExists(middlewarePath, middlewareTemplate);
14
+ await writeIfNotExists(
15
+ path.join(process.cwd(), 'src/app/core/Middleware/index.ts'),
16
+ `export * from './MiddlewareManager';`
17
+ );
33
18
 
34
19
  // Add to hooks.server.ts
35
20
  await injectIntoFile(
36
21
  hooksServerPath,
37
- `import { serverMiddleware } from '$core/Middleware/index.js';`,
38
22
  'import',
23
+ `import { serverMiddleware } from '$core';`,
39
24
  'before'
40
25
  );
41
26
 
@@ -50,7 +35,7 @@ export default async function initMiddleware() {
50
35
  await injectIntoFile(
51
36
  layoutPath,
52
37
  '<script',
53
- `\timport { clientMiddleware } from '$core/Middleware/index.js';\n\tclientMiddleware();`,
38
+ `\timport { clientMiddleware } from '$core';\n\tclientMiddleware();`,
54
39
  'after'
55
40
  );
56
41
 
@@ -3,7 +3,7 @@ import path from 'path';
3
3
 
4
4
  const schemaPath = path.join(process.cwd(), 'src/app/core/Schemas/Schema.ts');
5
5
 
6
- const schemaTemplate = `import { createSchemaFactory } from '@azure-net/kit/schema';
6
+ const schemaTemplate = `import { createSchemaFactory } from '@azure-net/kit';
7
7
  import { createRules, validationMessagesI18n } from '@azure-net/kit/schema';
8
8
 
9
9
  export const Schema = createSchemaFactory(createRules(validationMessagesI18n));`;
@@ -12,7 +12,7 @@ export default async function initSchema() {
12
12
  await writeIfNotExists(schemaPath, schemaTemplate);
13
13
  await writeIfNotExists(
14
14
  path.join(process.cwd(), 'src/app/core/Schemas/index.ts'),
15
- `export * from './Schema.js';`
15
+ `export * from './Schema';`
16
16
  );
17
17
 
18
18
  console.log('✅ Schema factory initialized');
@@ -2,7 +2,6 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { loadUserConfig } from '../../utils/loadConfig.js';
4
4
 
5
- const BASE_STRUCTURE = ['Domain', 'Infrastructure', 'Application', 'Delivery'];
6
5
  const APP_ROOT = path.join(process.cwd(), 'src', 'app');
7
6
  const CONTEXTS_PATH = path.join(APP_ROOT, 'contexts');
8
7
  const CORE_PATH = path.join(APP_ROOT, 'core');
@@ -16,31 +15,34 @@ async function createDirIfNotExists(dirPath) {
16
15
  }
17
16
  }
18
17
 
18
+ async function createIndexFile(dirPath, exportPatterns = []) {
19
+ const indexPath = path.join(dirPath, 'index.ts');
20
+ if (exportPatterns.length > 0) {
21
+ const content = exportPatterns.map(pattern => `export * from '${pattern}';`).join('\n') + '\n';
22
+ await fs.writeFile(indexPath, content, 'utf-8');
23
+ }
24
+ }
25
+
19
26
  async function createBaseStructure(root) {
20
- for (const section of BASE_STRUCTURE) {
21
- const sectionPath = path.join(root, section);
22
- await createDirIfNotExists(sectionPath);
27
+ // Domain
28
+ const domainPath = path.join(root, 'Domain');
29
+ await createDirIfNotExists(path.join(domainPath, 'Entities'));
30
+ await createDirIfNotExists(path.join(domainPath, 'Ports'));
23
31
 
24
- if (section === 'Infrastructure') {
25
- await createDirIfNotExists(path.join(sectionPath, 'Repositories'));
26
- await createDirIfNotExists(path.join(sectionPath, 'Datasources'));
27
- await createDirIfNotExists(path.join(sectionPath, 'Providers'));
28
- }
29
- if (section === 'Application') {
30
- await createDirIfNotExists(path.join(sectionPath, 'Services'));
31
- await createDirIfNotExists(path.join(sectionPath, 'Providers'));
32
- }
33
- if (section === 'Domain') {
34
- await createDirIfNotExists(path.join(sectionPath, 'Entities'));
35
- await createDirIfNotExists(path.join(sectionPath, 'Ports'));
36
- }
37
- if (section === 'Delivery') {
38
- await createDirIfNotExists(path.join(sectionPath, 'Presenters'));
39
- await createDirIfNotExists(path.join(sectionPath, 'Schemas'));
40
- await createDirIfNotExists(path.join(sectionPath, 'Actions'));
41
- await createDirIfNotExists(path.join(sectionPath, 'Stores'));
42
- }
43
- }
32
+ // Infrastructure
33
+ const infraPath = path.join(root, 'Infrastructure');
34
+ await createDirIfNotExists(path.join(infraPath, 'Http', 'Repositories'));
35
+ await createDirIfNotExists(path.join(infraPath, 'Http', 'Datasource'));
36
+ await createDirIfNotExists(path.join(infraPath, 'Providers'));
37
+
38
+ // Application
39
+ const appPath = path.join(root, 'Application');
40
+ await createDirIfNotExists(path.join(appPath, 'Services'));
41
+ await createDirIfNotExists(path.join(appPath, 'Providers'));
42
+
43
+ // Delivery - organized by modules, not by type
44
+ const deliveryPath = path.join(root, 'Delivery');
45
+ await createDirIfNotExists(deliveryPath);
44
46
  }
45
47
 
46
48
  export default async function initStructure() {
@@ -57,12 +59,21 @@ export default async function initStructure() {
57
59
 
58
60
  // Create core structure
59
61
  await createDirIfNotExists(CORE_PATH);
60
- await createDirIfNotExists(path.join(CORE_PATH, 'Datasources'));
61
- await createDirIfNotExists(path.join(CORE_PATH, 'Responses'));
62
- await createDirIfNotExists(path.join(CORE_PATH, 'Schemas'));
62
+ await createDirIfNotExists(path.join(CORE_PATH, 'Datasource'));
63
+ await createDirIfNotExists(path.join(CORE_PATH, 'Response'));
64
+ await createDirIfNotExists(path.join(CORE_PATH, 'Schema'));
63
65
  await createDirIfNotExists(path.join(CORE_PATH, 'Middleware'));
64
- await createDirIfNotExists(path.join(CORE_PATH, 'Presenters'));
65
- await createDirIfNotExists(path.join(CORE_PATH, 'Translations'));
66
+ await createDirIfNotExists(path.join(CORE_PATH, 'Presenter'));
67
+ await createDirIfNotExists(path.join(CORE_PATH, 'Translation'));
68
+
69
+ // Create core index.ts that exports all subdirectories
70
+ await createIndexFile(CORE_PATH, [
71
+ './Schema',
72
+ './Datasource',
73
+ './Response',
74
+ './Middleware',
75
+ './Translation'
76
+ ]);
66
77
 
67
78
  console.log('✅ Structure initialization complete!');
68
79
  }
@@ -1,11 +1,11 @@
1
1
  import { writeIfNotExists, updateIndexTs, injectIntoFile } from '../../utils/fileUtils.js';
2
2
  import path from 'path';
3
3
 
4
- const translationsPath = path.join(process.cwd(), 'src/app/core/Translations');
4
+ const translationsPath = path.join(process.cwd(), 'src/app/core/Translation');
5
5
  const layoutServerPath = path.join(process.cwd(), 'src/routes/+layout.server.ts');
6
6
  const layoutTsPath = path.join(process.cwd(), 'src/routes/+layout.ts');
7
7
 
8
- const translationProviderTemplate = `import { messages } from './Locales/index.js';
8
+ const translationProviderTemplate = `import { messages } from './Locales';
9
9
  import { createTranslations } from '@azure-net/kit/i18n';
10
10
 
11
11
  export const Translation = createTranslations({
@@ -28,8 +28,8 @@ const ruLocaleTemplate = `export default {
28
28
  };`;
29
29
 
30
30
  const localesIndexTemplate = `export const messages = {
31
- \ten: () => import('./en.js').then((res) => res.default),
32
- \tru: () => import('./ru.js').then((res) => res.default)
31
+ \ten: () => import('./en').then((res) => res.default),
32
+ \tru: () => import('./ru').then((res) => res.default)
33
33
  };`;
34
34
 
35
35
  export default async function initTranslations() {
@@ -54,13 +54,11 @@ export default async function initTranslations() {
54
54
  localesIndexTemplate
55
55
  );
56
56
 
57
- await updateIndexTs(translationsPath);
58
-
59
57
  // Add to hooks.server.ts
60
58
  await injectIntoFile(
61
59
  path.join(process.cwd(), 'src/hooks.server.ts'),
62
60
  'import',
63
- `import { Translation } from '$core/Translations/index.js';`,
61
+ `import { Translation } from '$core';`,
64
62
  'before'
65
63
  );
66
64
 
@@ -71,18 +69,25 @@ export default async function initTranslations() {
71
69
  'after'
72
70
  );
73
71
 
72
+ await injectIntoFile(
73
+ path.join(process.cwd(), 'src/hooks.server.ts'),
74
+ 'transformPageChunk: ({ html })',
75
+ ' => {\n\t\t\t\tconst serialized = serialize(html);\n\t\t\t\treturn applyHtmlLocaleAttr(serialized);\n\t\t\t}',
76
+ 'after'
77
+ );
78
+
74
79
  // Add to layout files
75
80
  await injectIntoFile(
76
81
  layoutServerPath,
77
82
  'export const load',
78
- `\treturn { lang: locals.lang, user: locals.user };`,
83
+ `\treturn { lang: locals.lang };`,
79
84
  'after'
80
85
  );
81
86
 
82
87
  await injectIntoFile(
83
88
  layoutTsPath,
84
89
  'import',
85
- `import { Translation } from '$core/Translations/index.js';`,
90
+ `import { Translation } from '$core';\nimport { browser } from '$app/environment';`,
86
91
  'before'
87
92
  );
88
93
 
@@ -20,7 +20,7 @@ export async function updateIndexTs(dir, filePattern = '.ts', ignore = ['index.t
20
20
  f.endsWith(filePattern) && !ignore.includes(f)
21
21
  );
22
22
  const content = files
23
- .map(f => `export * from './${f.replace('.ts', '')}.js';`)
23
+ .map(f => `export * from './${f.replace('.ts', '')}';`)
24
24
  .join('\n') + '\n';
25
25
  await fs.writeFile(path.join(dir, 'index.ts'), content, 'utf-8');
26
26
  } catch (error) {
@@ -28,6 +28,25 @@ export async function updateIndexTs(dir, filePattern = '.ts', ignore = ['index.t
28
28
  }
29
29
  }
30
30
 
31
+ export async function ensureIndexExports(layerPath) {
32
+ try {
33
+ const subdirs = await fs.readdir(layerPath, { withFileTypes: true });
34
+ const exportLines = subdirs
35
+ .filter(d => d.isDirectory())
36
+ .map(d => `export * from './${d.name}';`);
37
+
38
+ if (exportLines.length > 0) {
39
+ await fs.writeFile(
40
+ path.join(layerPath, 'index.ts'),
41
+ exportLines.join('\n') + '\n',
42
+ 'utf-8'
43
+ );
44
+ }
45
+ } catch (error) {
46
+ // Directory doesn't exist
47
+ }
48
+ }
49
+
31
50
  export async function injectIntoFile(filepath, searchPattern, newContent, position = 'after') {
32
51
  try {
33
52
  let content = await fs.readFile(filepath, 'utf-8');
@@ -4,7 +4,10 @@ import { toPascalCase, toCamelCase } from './contextUtils.js';
4
4
  import { writeIfNotExists, updateIndexTs } from './fileUtils.js';
5
5
 
6
6
  export async function ensureProvider(context, layer, dependencies = {}) {
7
- const providerName = `${toPascalCase(context)}${layer}Provider`;
7
+ const contextName = toPascalCase(context);
8
+ const providerName = `${layer}Provider`;
9
+ const boundaryName = `${contextName}${layer}BoundaryProvider`;
10
+
8
11
  const contextPath = path.join(process.cwd(), 'src/app/contexts', context);
9
12
  const providerPath = path.join(contextPath, layer, 'Providers', `${layer}Provider.ts`);
10
13
 
@@ -16,29 +19,38 @@ export async function ensureProvider(context, layer, dependencies = {}) {
16
19
  const content = generateProviderContent(context, layer, dependencies);
17
20
  await writeIfNotExists(providerPath, content);
18
21
  await updateIndexTs(path.dirname(providerPath));
19
- await updateIndexTs(path.join(contextPath, layer));
22
+
23
+ // Update layer index to export provider
24
+ const layerIndexPath = path.join(contextPath, layer, 'index.ts');
25
+ await writeIfNotExists(layerIndexPath, `export { ${providerName} } from './Providers';`);
26
+
20
27
  return { exists: false, name: providerName, path: providerPath };
21
28
  }
22
29
  }
23
30
 
24
31
  function generateProviderContent(context, layer, dependencies) {
25
- const providerName = `${toPascalCase(context)}${layer}BoundaryProvider`;
32
+ const contextName = toPascalCase(context);
33
+ const boundaryName = `${contextName}${layer}BoundaryProvider`;
26
34
  const varName = `${layer}Provider`;
27
35
 
28
36
  let imports = [`import { createBoundaryProvider } from '@azure-net/kit';`];
29
37
  let deps = [];
30
- let factories = [];
38
+ let depParams = '';
31
39
 
32
- if (dependencies.datasource && layer === 'Infrastructure') {
33
- imports.push(`import { ${dependencies.datasource} } from '../Datasources/index.js';`);
34
- factories.push(`\t${dependencies.datasource}: () => new ${dependencies.datasource}()`);
40
+ if (dependencies.hasDatasource && layer === 'Infrastructure') {
41
+ imports.push(`import { DatasourceProvider } from './DatasourceProvider';`);
42
+ deps.push('DatasourceProvider');
35
43
  }
36
44
 
37
45
  if (dependencies.infrastructure && layer === 'Application') {
38
- imports.push(`import { InfrastructureProvider } from '../../Infrastructure/index.js';`);
46
+ imports.push(`import { InfrastructureProvider } from '$${context}/Infrastructure';`);
39
47
  deps.push('InfrastructureProvider');
40
48
  }
41
49
 
50
+ if (deps.length > 0) {
51
+ depParams = `{ ${deps.join(', ')} }`;
52
+ }
53
+
42
54
  const dependsOnStr = deps.length > 0
43
55
  ? `,\n\t{ dependsOn: { ${deps.join(', ')} } }`
44
56
  : '';
@@ -46,32 +58,49 @@ function generateProviderContent(context, layer, dependencies) {
46
58
  return `${imports.join('\n')}
47
59
 
48
60
  export const ${varName} = createBoundaryProvider(
49
- \t'${providerName}',
50
- \t(${deps.length > 0 ? `{ ${deps.join(', ')} }` : ''}) => ({
51
- ${factories.join(',\n')}
61
+ \t'${boundaryName}',
62
+ \t(${depParams ? depParams : ''}) => ({
63
+ \t\t// Services will be added here
52
64
  \t})${dependsOnStr}
53
65
  );`;
54
66
  }
55
67
 
56
- export async function addToProvider(providerPath, serviceName, importPath) {
68
+ export async function addToProvider(providerPath, serviceName, importPath, dependency = null) {
57
69
  let content = await fs.readFile(providerPath, 'utf-8');
58
70
 
59
71
  // Add import if not exists
60
72
  const importStatement = `import { ${serviceName} } from '${importPath}';`;
61
- if (!content.includes(importStatement)) {
73
+ if (!content.includes(serviceName)) {
62
74
  const lastImportIndex = content.lastIndexOf('import ');
63
75
  const endOfImportLine = content.indexOf('\n', lastImportIndex);
64
76
  content = content.slice(0, endOfImportLine + 1) + importStatement + '\n' + content.slice(endOfImportLine + 1);
65
77
  }
66
78
 
67
- // Add factory method
68
- const factoryLine = `\t\t${serviceName}: () => new ${serviceName}()`;
69
- const returnIndex = content.indexOf('=> ({');
70
- const endOfReturn = content.indexOf('\t})', returnIndex);
79
+ // Add factory method with dependency
80
+ const factoryLine = dependency
81
+ ? `\t\t${serviceName}: () => new ${serviceName}(${dependency})`
82
+ : `\t\t${serviceName}: () => new ${serviceName}()`;
83
+
84
+ if (!content.includes(`${serviceName}:`)) {
85
+ // Find the position to insert
86
+ const returnIndex = content.indexOf('=> ({');
87
+ const endOfReturn = content.indexOf('\t})', returnIndex);
88
+
89
+ // Check if there are existing services
90
+ const servicesSection = content.substring(returnIndex, endOfReturn);
91
+ const hasServices = servicesSection.includes(':');
71
92
 
72
- if (!content.includes(factoryLine)) {
73
- const beforeEnd = content.lastIndexOf('\n', endOfReturn);
74
- content = content.slice(0, beforeEnd) + ',\n' + factoryLine + content.slice(beforeEnd);
93
+ if (hasServices) {
94
+ // Add comma before new service
95
+ const beforeEnd = content.lastIndexOf('\n', endOfReturn);
96
+ content = content.slice(0, beforeEnd) + ',\n' + factoryLine + content.slice(beforeEnd);
97
+ } else {
98
+ // First service, replace comment
99
+ content = content.replace(
100
+ '// Services will be added here',
101
+ factoryLine
102
+ );
103
+ }
75
104
  }
76
105
 
77
106
  await fs.writeFile(providerPath, content, 'utf-8');