@devstroupe/devkit-cli 1.1.15 → 1.2.0-beta.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.
Files changed (90) hide show
  1. package/README.md +18 -0
  2. package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +1 -1
  3. package/dist/boilerplates/angular-template/src/app/services/role.service.ts +1 -1
  4. package/dist/boilerplates/angular-template/src/app/services/user.service.ts +1 -1
  5. package/dist/boilerplates/nest-template/.dockerignore +8 -0
  6. package/dist/boilerplates/nest-template/package.json +13 -15
  7. package/dist/boilerplates/nest-template/src/app.module.ts +7 -10
  8. package/dist/boilerplates/nest-template/src/database/data-source.ts +4 -1
  9. package/dist/boilerplates/nest-template/src/database/migrations/1600000000000-create-roles.ts +2 -1
  10. package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +2 -2
  11. package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +1 -1
  12. package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +2 -7
  13. package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +13 -11
  14. package/dist/boilerplates/nest-template/src/modules/auth/jwt-auth.guard.ts +5 -6
  15. package/dist/boilerplates/nest-template/src/modules/core/core.module.ts +9 -0
  16. package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.orm-entity.ts +4 -1
  17. package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts +2 -2
  18. package/dist/boilerplates/nest-template/src/modules/role/infra/http/role.controller.ts +1 -1
  19. package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage.controller.ts +1 -1
  20. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +4 -1
  21. package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +2 -2
  22. package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +1 -1
  23. package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +6 -3
  24. package/dist/boilerplates/nest-template/src/platform/security/security.module.ts +20 -0
  25. package/dist/boilerplates/nest-template/src/platform/security/security.tokens.ts +4 -0
  26. package/dist/boilerplates/nest-template/src/platform/security/token-verification.module.ts +22 -0
  27. package/dist/boilerplates/nest-template/tsconfig.json +1 -1
  28. package/dist/generation/backend-generator.integration.test.d.ts +1 -0
  29. package/dist/generation/backend-generator.integration.test.js +232 -0
  30. package/dist/generation/backend-layout.d.ts +15 -0
  31. package/dist/generation/backend-layout.js +77 -0
  32. package/dist/generation/backend-layout.test.d.ts +1 -0
  33. package/dist/generation/backend-layout.test.js +48 -0
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.js +364 -205
  36. package/dist/migrations/entity-module-move.d.ts +16 -0
  37. package/dist/migrations/entity-module-move.js +98 -0
  38. package/dist/migrations/entity-module-move.test.d.ts +1 -0
  39. package/dist/migrations/entity-module-move.test.js +30 -0
  40. package/dist/rules/linter-rules.js +252 -22
  41. package/dist/rules/linter-rules.test.js +125 -0
  42. package/dist/templates/angular/service.template.d.ts +1 -1
  43. package/dist/templates/angular/service.template.js +2 -2
  44. package/dist/templates/generator-characterization.test.d.ts +1 -0
  45. package/dist/templates/generator-characterization.test.js +42 -0
  46. package/dist/templates/nest/crud.templates.d.ts +3 -3
  47. package/dist/templates/nest/crud.templates.js +36 -18
  48. package/dist/templates/nest/functional-module.templates.d.ts +10 -0
  49. package/dist/templates/nest/functional-module.templates.js +261 -0
  50. package/dist/templates/nest/index.d.ts +1 -1
  51. package/dist/templates/nest/index.js +1 -1
  52. package/dist/templates/nest/migration.template.js +6 -3
  53. package/dist/templates/relationship-templates.test.js +0 -4
  54. package/package.json +4 -4
  55. package/dist/boilerplates/angular-template/node_modules/.bin/acorn +0 -21
  56. package/dist/boilerplates/angular-template/node_modules/.bin/browserslist +0 -21
  57. package/dist/boilerplates/angular-template/node_modules/.bin/eslint +0 -21
  58. package/dist/boilerplates/angular-template/node_modules/.bin/jiti +0 -21
  59. package/dist/boilerplates/angular-template/node_modules/.bin/js-yaml +0 -21
  60. package/dist/boilerplates/angular-template/node_modules/.bin/lessc +0 -21
  61. package/dist/boilerplates/angular-template/node_modules/.bin/ng +0 -21
  62. package/dist/boilerplates/angular-template/node_modules/.bin/ng-xi18n +0 -21
  63. package/dist/boilerplates/angular-template/node_modules/.bin/ngc +0 -21
  64. package/dist/boilerplates/angular-template/node_modules/.bin/nx +0 -21
  65. package/dist/boilerplates/angular-template/node_modules/.bin/nx-cloud +0 -21
  66. package/dist/boilerplates/angular-template/node_modules/.bin/sass +0 -21
  67. package/dist/boilerplates/angular-template/node_modules/.bin/terser +0 -21
  68. package/dist/boilerplates/angular-template/node_modules/.bin/tsc +0 -21
  69. package/dist/boilerplates/angular-template/node_modules/.bin/tsserver +0 -21
  70. package/dist/boilerplates/angular-template/node_modules/.bin/vite +0 -21
  71. package/dist/boilerplates/angular-template/node_modules/.bin/vitest +0 -21
  72. package/dist/boilerplates/angular-template/node_modules/.bin/webpack +0 -21
  73. package/dist/boilerplates/angular-template/node_modules/.bin/yaml +0 -21
  74. package/dist/boilerplates/nest-template/node_modules/.bin/acorn +0 -21
  75. package/dist/boilerplates/nest-template/node_modules/.bin/nest +0 -21
  76. package/dist/boilerplates/nest-template/node_modules/.bin/prettier +0 -21
  77. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node +0 -21
  78. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-cwd +0 -21
  79. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-esm +0 -21
  80. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-script +0 -21
  81. package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-transpile-only +0 -21
  82. package/dist/boilerplates/nest-template/node_modules/.bin/ts-script +0 -21
  83. package/dist/boilerplates/nest-template/node_modules/.bin/tsc +0 -21
  84. package/dist/boilerplates/nest-template/node_modules/.bin/tsserver +0 -21
  85. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +0 -21
  86. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +0 -21
  87. package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +0 -21
  88. package/dist/boilerplates/nest-template/node_modules/.bin/webpack +0 -21
  89. package/dist/templates/nest/microservice.templates.d.ts +0 -4
  90. package/dist/templates/nest/microservice.templates.js +0 -157
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.angularServiceTemplate = angularServiceTemplate;
4
4
  const names_1 = require("../shared/names");
5
- function angularServiceTemplate(name) {
5
+ function angularServiceTemplate(name, moduleName = 'core') {
6
6
  const pascalName = (0, names_1.kebabToPascal)(name);
7
7
  return `import { Injectable } from '@angular/core';
8
8
  import { HttpClient } from '@angular/common/http';
@@ -13,7 +13,7 @@ import { BaseService } from '@devstroupe/devkit-angular';
13
13
  })
14
14
  export class ${pascalName}Service extends BaseService<any> {
15
15
  constructor(http: HttpClient) {
16
- super(http, '/api/${name}');
16
+ super(http, '/api/${moduleName}/${name}');
17
17
  }
18
18
  }
19
19
  `;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const nest_1 = require("./nest");
9
+ const functional_module_templates_1 = require("./nest/functional-module.templates");
10
+ (0, node_test_1.describe)('backend generator characterization', () => {
11
+ (0, node_test_1.it)('keeps the monolith HTTP and Nest module contracts explicit', () => {
12
+ const controller = (0, nest_1.nestControllerTemplate)('order-item');
13
+ const module = (0, nest_1.nestModuleTemplate)('order-item');
14
+ strict_1.default.match(controller, /export class OrderItemController/);
15
+ strict_1.default.match(controller, /extends BaseCrudController/);
16
+ strict_1.default.match(module, /TypeOrmModule\.forFeature\(\[OrderItemOrmEntity\]\)/);
17
+ strict_1.default.match(module, /export class OrderItemModule/);
18
+ });
19
+ (0, node_test_1.it)('documents stateless HTTP routing by functional module', () => {
20
+ const gateway = (0, functional_module_templates_1.nestGatewayModuleProxyControllerTemplate)('orders', 4010);
21
+ const gatewayModule = (0, functional_module_templates_1.nestGatewayFunctionalModuleTemplate)('orders', 4010);
22
+ const controller = (0, functional_module_templates_1.nestControllerV2Template)('order-item', 'orders');
23
+ strict_1.default.match(gateway, /@Controller\('orders'\)/);
24
+ strict_1.default.match(gateway, /authenticateGatewayRequest/);
25
+ strict_1.default.match(gateway, /ORDERS_SERVICE_HOST/);
26
+ strict_1.default.match(gateway, /proxy\.forward/);
27
+ strict_1.default.doesNotMatch(gateway, /ClientProxy|MessagePattern|create_order-item/);
28
+ strict_1.default.match(gatewayModule, /ModuleHttpProxy/);
29
+ strict_1.default.doesNotMatch(gatewayModule, /Transport\.TCP/);
30
+ strict_1.default.match(controller, /@Controller\('orders\/order-item'\)/);
31
+ strict_1.default.match(controller, /extends BaseCrudController/);
32
+ });
33
+ (0, node_test_1.it)('documents tenant-scoped persistence generated today', () => {
34
+ const ormEntity = (0, nest_1.nestOrmEntityTemplate)('invoice', [], 'invoices', true);
35
+ const repository = (0, nest_1.nestRepositoryAdapterTemplate)('invoice', [], true);
36
+ strict_1.default.match(ormEntity, /tenantId!: string/);
37
+ strict_1.default.match(ormEntity, /@DeleteDateColumn/);
38
+ strict_1.default.match(repository, /executionContext\.requireTenantId\(\)/);
39
+ strict_1.default.doesNotMatch(repository, /x-tenant-id/);
40
+ strict_1.default.doesNotMatch(repository, /REQUEST/);
41
+ });
42
+ });
@@ -10,18 +10,18 @@ export declare function nestOrmEntityTemplate(name: string, properties: IDevkitE
10
10
  unique?: boolean;
11
11
  name?: string;
12
12
  }>, persistence?: {
13
- deletion?: 'soft-delete' | 'hard-delete';
13
+ deletion?: 'soft-delete';
14
14
  mode?: 'append-only' | 'standard';
15
15
  }, automaticFields?: {
16
16
  timestamps?: boolean;
17
17
  blamers?: boolean;
18
18
  optimisticLocking?: boolean;
19
- }): string;
19
+ }, relationImportPrefix?: string): string;
20
20
  export declare function nestRepositoryAdapterTemplate(name: string, properties: IDevkitEntityProperty[], isTenantScoped: boolean): string;
21
21
  export declare function nestDtoTemplate(name: string, properties: IDevkitEntityProperty[]): string;
22
22
  export declare function nestServiceTemplate(name: string): string;
23
23
  export declare function nestControllerTemplate(name: string, persistence?: {
24
- deletion?: 'soft-delete' | 'hard-delete';
24
+ deletion?: 'soft-delete';
25
25
  mode?: 'append-only' | 'standard';
26
26
  }): string;
27
27
  export declare function nestModuleTemplate(name: string): string;
@@ -25,12 +25,15 @@ function nestDomainEntityTemplate(name, properties, isTenantScoped, automaticFie
25
25
  return ` ${relation.foreignKey}!: number;\n ${relation.relationName}?: any;`;
26
26
  }
27
27
  let typeStr = 'string';
28
- if (p.type === 'number' || p.type === 'decimal' || p.type === 'currency') {
28
+ if (p.type === 'number' || p.type === 'decimal' || p.type === 'currency' || p.type === 'integer') {
29
29
  typeStr = 'number';
30
30
  }
31
31
  else if (p.type === 'boolean') {
32
32
  typeStr = 'boolean';
33
33
  }
34
+ else if (p.type === 'datepicker' || p.type === 'datetime' || p.type === 'date') {
35
+ typeStr = 'string | Date';
36
+ }
34
37
  else if (p.type === 'enum' && p.enumOptions) {
35
38
  typeStr = p.enumOptions.map(o => `'${o}'`).join(' | ');
36
39
  }
@@ -68,7 +71,7 @@ export interface I${pascalName}Repository {
68
71
  }
69
72
  `;
70
73
  }
71
- function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, indexes, persistence, automaticFields) {
74
+ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, indexes, persistence, automaticFields, relationImportPrefix = '../../../') {
72
75
  const pascalName = (0, names_1.kebabToPascal)(name);
73
76
  const relationships = (0, relationships_1.getRelationships)(properties);
74
77
  const typeormImports = ['Entity', 'PrimaryGeneratedColumn', 'Column'];
@@ -86,8 +89,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
86
89
  typeormImports.push('JoinTable');
87
90
  if ((indexes && indexes.length > 0) || properties.some((p) => p.index))
88
91
  typeormImports.push('Index');
89
- if (persistence?.deletion === 'soft-delete')
90
- typeormImports.push('DeleteDateColumn');
92
+ typeormImports.push('DeleteDateColumn');
91
93
  if (automaticFields?.timestamps)
92
94
  typeormImports.push('CreateDateColumn', 'UpdateDateColumn');
93
95
  if (automaticFields?.optimisticLocking)
@@ -95,7 +97,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
95
97
  const relImports = [...new Set(relationships.map((relation) => relation.target))]
96
98
  .map((targetKebab) => {
97
99
  const targetPascal = (0, names_1.kebabToPascal)(targetKebab);
98
- return `import { ${targetPascal}OrmEntity } from '../../../${targetKebab}/infra/database/${targetKebab}.orm-entity';`;
100
+ return `import { ${targetPascal}OrmEntity } from '${relationImportPrefix}${targetKebab}/infrastructure/persistence/typeorm/${targetKebab}.orm-entity';`;
99
101
  })
100
102
  .join('\n');
101
103
  const propsCode = properties
@@ -181,8 +183,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
181
183
  .join('\n\n');
182
184
  const tenantField = isTenantScoped ? '\n\n @Column()\n tenantId!: string;' : '';
183
185
  const relImportsStr = relImports ? `\n${relImports}` : '';
184
- const isSoftDelete = persistence?.deletion === 'soft-delete';
185
- const softDeleteField = isSoftDelete ? '\n\n @DeleteDateColumn({ name: \'deleted_at\', nullable: true })\n deletedAt?: Date;' : '';
186
+ const softDeleteField = '\n\n @DeleteDateColumn({ name: \'deleted_at\', nullable: true })\n deletedAt?: Date;';
186
187
  let autoFieldsStr = '';
187
188
  if (automaticFields?.timestamps) {
188
189
  autoFieldsStr += '\n\n @CreateDateColumn({ name: \'created_at\' })\n createdAt!: Date;\n\n @UpdateDateColumn({ name: \'updated_at\' })\n updatedAt!: Date;';
@@ -195,11 +196,16 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
195
196
  }
196
197
  const indexDecorators = indexes && indexes.length > 0
197
198
  ? indexes.map(idx => {
198
- const nameOption = idx.name ? `name: '${idx.name}'` : '';
199
199
  const uniqueOption = idx.unique ? `unique: true` : '';
200
- const options = [nameOption, uniqueOption].filter(Boolean).join(', ');
201
- const optionsStr = options ? `, { ${options} }` : '';
202
- return `@Index(${JSON.stringify(idx.columns)}${optionsStr})`;
200
+ const nameOption = idx.name ? `'${idx.name}'` : '';
201
+ if (nameOption) {
202
+ const optStr = uniqueOption ? `, { ${uniqueOption} }` : '';
203
+ return `@Index(${nameOption}, ${JSON.stringify(idx.columns)}${optStr})`;
204
+ }
205
+ else {
206
+ const optStr = uniqueOption ? `, { ${uniqueOption} }` : '';
207
+ return `@Index(${JSON.stringify(idx.columns)}${optStr})`;
208
+ }
203
209
  }).join('\n') + '\n'
204
210
  : '';
205
211
  return `import { ${typeormImports.join(', ')} } from 'typeorm';${relImportsStr}
@@ -215,9 +221,9 @@ ${propsCode}${tenantField}${softDeleteField}${autoFieldsStr}
215
221
  }
216
222
  function nestRepositoryAdapterTemplate(name, properties, isTenantScoped) {
217
223
  const pascalName = (0, names_1.kebabToPascal)(name);
218
- const tenantInject = isTenantScoped ? `import { Inject, Scope } from '@nestjs/common';\nimport { REQUEST } from '@nestjs/core';\n` : '';
219
- const tenantConstructor = isTenantScoped ? `,\n @Inject(REQUEST) private readonly request: any` : '';
220
- const tenantIdGetter = isTenantScoped ? `\n private get tenantId(): string {\n return this.request?.data?.tenantId || this.request?.tenantId || this.request?.headers?.['x-tenant-id'] || 'default';\n }\n` : '';
224
+ const tenantInject = isTenantScoped ? `import { executionContext } from '@devstroupe/devkit-nest';\n` : '';
225
+ const tenantConstructor = '';
226
+ const tenantIdGetter = isTenantScoped ? `\n private get tenantId(): string {\n return executionContext.requireTenantId();\n }\n` : '';
221
227
  const relationships = (0, relationships_1.getRelationships)(properties);
222
228
  const relKeys = relationships
223
229
  .filter((relation) => relation.kind !== 'one-to-many')
@@ -233,7 +239,7 @@ function nestRepositoryAdapterTemplate(name, properties, isTenantScoped) {
233
239
  const relation = (0, relationships_1.getRelationships)([property])[0];
234
240
  return (0, relationships_1.isSingularRelationship)(relation.kind) ? [relation.foreignKey] : [];
235
241
  });
236
- const relationsOption = relKeys.length > 0 ? `, relations: [${relKeys.map(r => `'${r}'`).join(', ')}]` : '';
242
+ const relationsOption = relKeys.length > 0 ? `, relations: { ${relKeys.map(r => `${r}: true`).join(', ')} }` : '';
237
243
  const joinsCode = relKeys.map((r) => `\n qb.leftJoinAndSelect(\`\${alias}.${r}\`, '${r}');`).join('');
238
244
  const mapRels = relKeys.map((r) => ` if (orm.${r}) domain.${r} = orm.${r};`).join('\n');
239
245
  const mapToDomainProperties = properties
@@ -245,6 +251,9 @@ function nestRepositoryAdapterTemplate(name, properties, isTenantScoped) {
245
251
  .join('\n');
246
252
  const mapToOrmProperties = persistedProperties.map((property) => {
247
253
  if (property.type !== 'relationship') {
254
+ if (property.type === 'datepicker' || property.type === 'datetime' || property.type === 'date') {
255
+ return ` if (domain.${property.name} !== undefined) orm.${property.name} = typeof domain.${property.name} === 'string' ? new Date(domain.${property.name}) : domain.${property.name};`;
256
+ }
248
257
  return ` if (domain.${property.name} !== undefined) orm.${property.name} = domain.${property.name};`;
249
258
  }
250
259
  const relation = (0, relationships_1.getRelationships)([property])[0];
@@ -262,7 +271,7 @@ import { ${pascalName}OrmEntity } from './${name}.orm-entity';
262
271
  import { ${pascalName} } from '../../domain/${name}';
263
272
  import { I${pascalName}Repository } from '../../domain/${name}.repository.port';
264
273
  ${tenantInject}
265
- @Injectable(${isTenantScoped ? '{ scope: Scope.REQUEST }' : ''})
274
+ @Injectable()
266
275
  export class ${pascalName}RepositoryAdapter extends BaseRepository<${pascalName}OrmEntity> implements I${pascalName}Repository {
267
276
  constructor(
268
277
  @InjectRepository(${pascalName}OrmEntity)
@@ -314,7 +323,10 @@ export class ${pascalName}RepositoryAdapter extends BaseRepository<${pascalName}
314
323
  };
315
324
  }
316
325
 
317
- async delete(id: number): Promise<void> {${isTenantScoped ? `\n const entity = await this.findOne(id);\n if (entity) {\n await this.ormRepository.delete(id);\n }` : `\n await this.ormRepository.delete(id);`}
326
+ async delete(id: number): Promise<void> {
327
+ const qb = this.ormRepository.createQueryBuilder('${name}').softDelete().where('id = :id', { id });${isTenantScoped ? `\n qb.andWhere('tenantId = :tenantId', { tenantId: this.tenantId });` : ''}
328
+ qb.andWhere('deleted_at IS NULL');
329
+ await qb.execute();
318
330
  }
319
331
 
320
332
  private mapToDomain(orm: ${pascalName}OrmEntity): ${pascalName} {
@@ -445,8 +457,14 @@ export class Filter${pascalName}Dto {
445
457
  @ApiProperty({ required: false })
446
458
  direction?: 'ASC' | 'DESC';
447
459
 
460
+ @ApiProperty({ required: false })
461
+ cursor?: string;
462
+
463
+ @ApiProperty({ required: false })
464
+ includeTotal?: boolean;
465
+
448
466
  @ApiProperty({ required: false, type: Object })
449
- filter?: Record<string, any>;
467
+ filter?: Record<string, unknown>;
450
468
  }
451
469
  `;
452
470
  }
@@ -0,0 +1,10 @@
1
+ import type { IDevkitEntityConfig, IDevkitEntityProperty } from '@devstroupe/devkit-core';
2
+ export declare function nestFunctionalModuleTemplate(moduleName: string, entities: IDevkitEntityConfig[], microservice?: boolean): string;
3
+ export declare function nestRepositoryPortV2Template(name: string): string;
4
+ export declare function nestRepositoryAdapterV2Template(name: string, properties: IDevkitEntityProperty[], tenantScoped: boolean): string;
5
+ export type CrudUseCaseKind = 'create' | 'update' | 'find' | 'list' | 'soft-delete';
6
+ export declare function nestCrudUseCaseTemplate(name: string, kind: CrudUseCaseKind): string;
7
+ export declare function nestCrudApplicationTemplate(name: string): string;
8
+ export declare function nestControllerV2Template(name: string, moduleName?: string): string;
9
+ export declare function nestGatewayFunctionalModuleTemplate(moduleName: string, port: number): string;
10
+ export declare function nestGatewayModuleProxyControllerTemplate(moduleName: string, port: number): string;
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nestFunctionalModuleTemplate = nestFunctionalModuleTemplate;
4
+ exports.nestRepositoryPortV2Template = nestRepositoryPortV2Template;
5
+ exports.nestRepositoryAdapterV2Template = nestRepositoryAdapterV2Template;
6
+ exports.nestCrudUseCaseTemplate = nestCrudUseCaseTemplate;
7
+ exports.nestCrudApplicationTemplate = nestCrudApplicationTemplate;
8
+ exports.nestControllerV2Template = nestControllerV2Template;
9
+ exports.nestGatewayFunctionalModuleTemplate = nestGatewayFunctionalModuleTemplate;
10
+ exports.nestGatewayModuleProxyControllerTemplate = nestGatewayModuleProxyControllerTemplate;
11
+ const names_1 = require("../shared/names");
12
+ const relationships_1 = require("../shared/relationships");
13
+ function constantName(value) {
14
+ return value.replace(/-/g, '_').toUpperCase();
15
+ }
16
+ function entityImports(entity, microservice) {
17
+ const name = entity.name;
18
+ const pascal = (0, names_1.kebabToPascal)(name);
19
+ const controllerSuffix = microservice ? 'MsController' : 'Controller';
20
+ const controllerFile = microservice ? `${name}.ms.controller` : `${name}.controller`;
21
+ return [
22
+ `import { ${pascal}OrmEntity } from './${name}/infrastructure/persistence/typeorm/${name}.orm-entity';`,
23
+ `import { ${pascal}RepositoryAdapter, ${constantName(name)}_REPOSITORY } from './${name}/infrastructure/persistence/typeorm/${name}.repository';`,
24
+ `import { ${pascal}${controllerSuffix} } from './${name}/api/${controllerFile}';`,
25
+ `import { Create${pascal}UseCase } from './${name}/application/use-cases/create-${name}.use-case';`,
26
+ `import { Update${pascal}UseCase } from './${name}/application/use-cases/update-${name}.use-case';`,
27
+ `import { Find${pascal}UseCase } from './${name}/application/use-cases/find-${name}.use-case';`,
28
+ `import { List${pascal}UseCase } from './${name}/application/use-cases/list-${name}.use-case';`,
29
+ `import { SoftDelete${pascal}UseCase } from './${name}/application/use-cases/soft-delete-${name}.use-case';`,
30
+ `import { ${pascal}CrudApplication } from './${name}/application/use-cases/${name}-crud.application';`,
31
+ ];
32
+ }
33
+ function nestFunctionalModuleTemplate(moduleName, entities, microservice = false) {
34
+ const modulePascal = (0, names_1.kebabToPascal)(moduleName);
35
+ const imports = entities.flatMap((entity) => entityImports(entity, microservice)).join('\n');
36
+ const ormEntities = entities.map((entity) => `${(0, names_1.kebabToPascal)(entity.name)}OrmEntity`).join(', ');
37
+ const controllers = entities.map((entity) => `${(0, names_1.kebabToPascal)(entity.name)}${microservice ? 'MsController' : 'Controller'}`).join(', ');
38
+ const providers = entities.flatMap((entity) => {
39
+ const pascal = (0, names_1.kebabToPascal)(entity.name);
40
+ return [
41
+ `{ provide: ${constantName(entity.name)}_REPOSITORY, useClass: ${pascal}RepositoryAdapter }`,
42
+ `Create${pascal}UseCase`, `Update${pascal}UseCase`, `Find${pascal}UseCase`,
43
+ `List${pascal}UseCase`, `SoftDelete${pascal}UseCase`, `${pascal}CrudApplication`,
44
+ ];
45
+ }).join(',\n ');
46
+ const exported = entities.map((entity) => `${(0, names_1.kebabToPascal)(entity.name)}CrudApplication`).join(', ');
47
+ const coreImports = moduleName === 'core'
48
+ ? `import { AuthModule } from '../auth/auth.module';\nimport { StorageDownloadModule } from '../storage/storage.module';\n`
49
+ : '';
50
+ const moduleImports = moduleName === 'core'
51
+ ? `TypeOrmModule.forFeature([${ormEntities}]), AuthModule, StorageDownloadModule`
52
+ : `TypeOrmModule.forFeature([${ormEntities}])`;
53
+ const moduleExports = moduleName === 'core'
54
+ ? [exported, 'AuthModule', 'StorageDownloadModule'].filter(Boolean).join(', ')
55
+ : exported;
56
+ return `import { Module } from '@nestjs/common';
57
+ import { TypeOrmModule } from '@nestjs/typeorm';
58
+ ${coreImports}${imports}
59
+
60
+ @Module({
61
+ imports: [${moduleImports}],
62
+ controllers: [${controllers}],
63
+ providers: [
64
+ ${providers}
65
+ ],
66
+ exports: [${moduleExports}],
67
+ })
68
+ export class ${modulePascal}Module {}
69
+ `;
70
+ }
71
+ function nestRepositoryPortV2Template(name) {
72
+ const pascal = (0, names_1.kebabToPascal)(name);
73
+ return `import type { CrudRepositoryPort } from '@devstroupe/devkit-nest';
74
+ import type { Filter${pascal}Dto } from '../../api/${name}.dto';
75
+ import type { ${pascal}OrmEntity } from '../../infrastructure/persistence/typeorm/${name}.orm-entity';
76
+
77
+ export type I${pascal}Repository = CrudRepositoryPort<${pascal}OrmEntity, number, Filter${pascal}Dto>;
78
+ `;
79
+ }
80
+ function nestRepositoryAdapterV2Template(name, properties, tenantScoped) {
81
+ const pascal = (0, names_1.kebabToPascal)(name);
82
+ const constant = `${constantName(name)}_REPOSITORY`;
83
+ const fields = properties.flatMap((property) => {
84
+ if (property.type !== 'relationship')
85
+ return [property.name];
86
+ const relation = (0, relationships_1.getRelationships)([property])[0];
87
+ return (0, relationships_1.isSingularRelationship)(relation.kind) && relation.foreignKey
88
+ ? [relation.foreignKey]
89
+ : [];
90
+ });
91
+ const searchable = properties
92
+ .filter((property) => property.type === 'string' || property.type === 'text')
93
+ .map((property) => property.name);
94
+ const options = tenantScoped
95
+ ? `, { alias: '${name}', scope: () => ({ tenantId: executionContext.requireTenantId() }) }`
96
+ : `, { alias: '${name}' }`;
97
+ return `import { Injectable } from '@nestjs/common';
98
+ import { InjectRepository } from '@nestjs/typeorm';
99
+ import { Repository } from 'typeorm';
100
+ import { BaseRepository, executionContext } from '@devstroupe/devkit-nest';
101
+ import { ${pascal}OrmEntity } from './${name}.orm-entity';
102
+ import type { Filter${pascal}Dto } from '../../../api/${name}.dto';
103
+ import type { I${pascal}Repository } from '../../../application/ports/${name}.repository.port';
104
+
105
+ export const ${constant} = Symbol('${constant}');
106
+
107
+ @Injectable()
108
+ export class ${pascal}RepositoryAdapter
109
+ extends BaseRepository<${pascal}OrmEntity, number, Filter${pascal}Dto>
110
+ implements I${pascal}Repository {
111
+ protected allowedFilters = ${JSON.stringify(fields)} as const;
112
+ protected allowedSorts = ${JSON.stringify(fields)} as const;
113
+ protected searchableFields = ${JSON.stringify(searchable.length ? searchable : ['id'])} as const;
114
+
115
+ constructor(@InjectRepository(${pascal}OrmEntity) repository: Repository<${pascal}OrmEntity>) {
116
+ super(repository${options});
117
+ }
118
+ }
119
+ `;
120
+ }
121
+ function nestCrudUseCaseTemplate(name, kind) {
122
+ const pascal = (0, names_1.kebabToPascal)(name);
123
+ const classPrefix = {
124
+ create: 'Create', update: 'Update', find: 'Find', list: 'List', 'soft-delete': 'SoftDelete',
125
+ };
126
+ const baseClass = {
127
+ create: 'CreateUseCase', update: 'UpdateUseCase', find: 'FindOneUseCase', list: 'ListUseCase', 'soft-delete': 'SoftDeleteUseCase',
128
+ };
129
+ const prefix = classPrefix[kind];
130
+ const base = baseClass[kind];
131
+ return `import { Inject, Injectable } from '@nestjs/common';
132
+ import { ${base} } from '@devstroupe/devkit-nest';
133
+ import { ${pascal}OrmEntity } from '../../infrastructure/persistence/typeorm/${name}.orm-entity';
134
+ import { ${constantName(name)}_REPOSITORY } from '../../infrastructure/persistence/typeorm/${name}.repository';
135
+ import type { I${pascal}Repository } from '../ports/${name}.repository.port';
136
+ import type { Filter${pascal}Dto } from '../../api/${name}.dto';
137
+
138
+ @Injectable()
139
+ export class ${prefix}${pascal}UseCase extends ${base}<${pascal}OrmEntity, number, Filter${pascal}Dto> {
140
+ constructor(@Inject(${constantName(name)}_REPOSITORY) repository: I${pascal}Repository) {
141
+ super(repository);
142
+ }
143
+ }
144
+ `;
145
+ }
146
+ function nestCrudApplicationTemplate(name) {
147
+ const pascal = (0, names_1.kebabToPascal)(name);
148
+ const camel = (0, names_1.kebabToCamel)(name);
149
+ return `import { Injectable } from '@nestjs/common';
150
+ import type { DeepPartial } from 'typeorm';
151
+ import type { IPaginator } from '@devstroupe/devkit-core';
152
+ import type { CrudApplication } from '@devstroupe/devkit-nest';
153
+ import { ${pascal}OrmEntity } from '../../infrastructure/persistence/typeorm/${name}.orm-entity';
154
+ import type { Filter${pascal}Dto } from '../../api/${name}.dto';
155
+ import { Create${pascal}UseCase } from './create-${name}.use-case';
156
+ import { Update${pascal}UseCase } from './update-${name}.use-case';
157
+ import { Find${pascal}UseCase } from './find-${name}.use-case';
158
+ import { List${pascal}UseCase } from './list-${name}.use-case';
159
+ import { SoftDelete${pascal}UseCase } from './soft-delete-${name}.use-case';
160
+
161
+ @Injectable()
162
+ export class ${pascal}CrudApplication implements CrudApplication<
163
+ ${pascal}OrmEntity, Filter${pascal}Dto, number,
164
+ DeepPartial<${pascal}OrmEntity>, DeepPartial<${pascal}OrmEntity>, IPaginator<${pascal}OrmEntity>
165
+ > {
166
+ constructor(
167
+ private readonly createUseCase: Create${pascal}UseCase,
168
+ private readonly updateUseCase: Update${pascal}UseCase,
169
+ private readonly findUseCase: Find${pascal}UseCase,
170
+ private readonly listUseCase: List${pascal}UseCase,
171
+ private readonly softDeleteUseCase: SoftDelete${pascal}UseCase,
172
+ ) {}
173
+
174
+ create(input: DeepPartial<${pascal}OrmEntity>) { return this.createUseCase.execute(input); }
175
+ update(id: number, input: DeepPartial<${pascal}OrmEntity>) { return this.updateUseCase.execute(id, input); }
176
+ findOne(id: number) { return this.findUseCase.execute(id); }
177
+ findMany(filter: Filter${pascal}Dto) { return this.listUseCase.execute(filter); }
178
+ delete(id: number) { return this.softDeleteUseCase.execute(id); }
179
+ }
180
+ `;
181
+ }
182
+ function nestControllerV2Template(name, moduleName = 'core') {
183
+ const pascal = (0, names_1.kebabToPascal)(name);
184
+ return `import { Controller, UseGuards } from '@nestjs/common';
185
+ import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
186
+ import { BaseCrudController } from '@devstroupe/devkit-nest';
187
+ import type { DeepPartial } from 'typeorm';
188
+ import { JwtAuthGuard } from '../../../auth/jwt-auth.guard';
189
+ import { ${pascal}OrmEntity } from '../infrastructure/persistence/typeorm/${name}.orm-entity';
190
+ import { ${pascal}CrudApplication } from '../application/use-cases/${name}-crud.application';
191
+ import { Filter${pascal}Dto } from './${name}.dto';
192
+
193
+ @Controller('${moduleName}/${name}')
194
+ @ApiTags('${name}')
195
+ @ApiBearerAuth()
196
+ @UseGuards(JwtAuthGuard)
197
+ export class ${pascal}Controller extends BaseCrudController<
198
+ ${pascal}OrmEntity, Filter${pascal}Dto, number,
199
+ DeepPartial<${pascal}OrmEntity>, DeepPartial<${pascal}OrmEntity>
200
+ > {
201
+ constructor(application: ${pascal}CrudApplication) { super(application); }
202
+ }
203
+ `;
204
+ }
205
+ function nestGatewayFunctionalModuleTemplate(moduleName, port) {
206
+ const pascal = (0, names_1.kebabToPascal)(moduleName);
207
+ return `import { Module } from '@nestjs/common';
208
+ import { ModuleHttpProxy } from '@devstroupe/devkit-nest';
209
+ import { ${pascal}GatewayController } from './${moduleName}.gateway.controller';
210
+
211
+ @Module({
212
+ controllers: [${pascal}GatewayController],
213
+ providers: [ModuleHttpProxy],
214
+ })
215
+ export class ${pascal}GatewayModule {}
216
+ `;
217
+ }
218
+ function nestGatewayModuleProxyControllerTemplate(moduleName, port) {
219
+ const pascal = (0, names_1.kebabToPascal)(moduleName);
220
+ const env = constantName(moduleName);
221
+ const publicPaths = moduleName === 'core'
222
+ ? `['/api/core/auth/login', '/api/core/auth/refresh', '/api/core/auth/register']`
223
+ : '[]';
224
+ return `import { All, Controller, Inject, Req, Res } from '@nestjs/common';
225
+ import type { Request, Response } from 'express';
226
+ import {
227
+ authenticateGatewayRequest,
228
+ GatewayProxyRequest,
229
+ ModuleHttpProxy,
230
+ TokenVerifier,
231
+ } from '@devstroupe/devkit-nest';
232
+ import { TOKEN_VERIFIER } from '../../platform/security/security.tokens';
233
+
234
+ @Controller('${moduleName}')
235
+ export class ${pascal}GatewayController {
236
+ constructor(
237
+ private readonly proxy: ModuleHttpProxy,
238
+ @Inject(TOKEN_VERIFIER) private readonly tokenVerifier: TokenVerifier,
239
+ ) {}
240
+
241
+ @All()
242
+ forwardRoot(@Req() request: Request, @Res() response: Response) {
243
+ return this.forward(request, response);
244
+ }
245
+
246
+ @All('*path')
247
+ forwardPath(@Req() request: Request, @Res() response: Response) {
248
+ return this.forward(request, response);
249
+ }
250
+
251
+ private async forward(request: Request, response: Response): Promise<void> {
252
+ await authenticateGatewayRequest(request as GatewayProxyRequest, this.tokenVerifier, {
253
+ publicPaths: ${publicPaths},
254
+ });
255
+ const target = process.env.${env}_SERVICE_URL
256
+ || \`http://\${process.env.${env}_SERVICE_HOST || 'localhost'}:\${process.env.${env}_SERVICE_PORT || '${port}'}\`;
257
+ await this.proxy.forward(target, request as GatewayProxyRequest, response);
258
+ }
259
+ }
260
+ `;
261
+ }
@@ -1,3 +1,3 @@
1
1
  export * from './crud.templates';
2
- export * from './microservice.templates';
3
2
  export * from './migration.template';
3
+ export * from './functional-module.templates';
@@ -15,5 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./crud.templates"), exports);
18
- __exportStar(require("./microservice.templates"), exports);
19
18
  __exportStar(require("./migration.template"), exports);
19
+ __exportStar(require("./functional-module.templates"), exports);
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nestMigrationTemplate = nestMigrationTemplate;
4
4
  exports.nestDataSourceTemplate = nestDataSourceTemplate;
5
+ const devkit_core_1 = require("@devstroupe/devkit-core");
5
6
  const names_1 = require("../shared/names");
6
7
  const relationships_1 = require("../shared/relationships");
7
8
  function columnType(property) {
@@ -47,7 +48,8 @@ function nestMigrationTemplate(entity, entities, timestamp, properties = entity.
47
48
  const className = `Create${(0, names_1.kebabToPascal)(entity.name)}${timestamp}`;
48
49
  const tableName = entity.tableName ?? `${entity.name}s`;
49
50
  const relationships = (0, relationships_1.getRelationships)(properties);
50
- const isSoftDelete = entity.persistence?.deletion === 'soft-delete';
51
+ const isSoftDelete = true;
52
+ const ownerModule = (0, devkit_core_1.resolveEntityModule)(entity);
51
53
  const scalarColumns = properties
52
54
  .filter((property) => property.type !== 'relationship')
53
55
  .map((property) => {
@@ -179,7 +181,8 @@ function nestMigrationTemplate(entity, entities, timestamp, properties = entity.
179
181
  return ` await queryRunner.query('ALTER TABLE \`${tableName}\` DROP CONSTRAINT \`${checkName}\`');`;
180
182
  }).join('\n')
181
183
  : '';
182
- return `import { MigrationInterface, QueryRunner, Table, TableForeignKey, TableIndex } from 'typeorm';
184
+ return `// owner-module: ${ownerModule}
185
+ import { MigrationInterface, QueryRunner, Table, TableForeignKey, TableIndex } from 'typeorm';
183
186
 
184
187
  export class ${className} implements MigrationInterface {
185
188
  name = '${className}';
@@ -217,7 +220,7 @@ export default new DataSource({
217
220
  username: process.env.DB_USER || 'root',
218
221
  password: process.env.DB_PASSWORD || 'root',
219
222
  database: process.env.DB_NAME || 'devstroupe',
220
- entities: [path.join(__dirname, '../modules/**/infra/database/*.orm-entity{.ts,.js}')],
223
+ entities: [path.join(__dirname, '../modules/**/infrastructure/persistence/typeorm/*.orm-entity{.ts,.js}')],
221
224
  migrations: [path.join(__dirname, './migrations/*{.ts,.js}')],
222
225
  synchronize: false
223
226
  });
@@ -9,7 +9,6 @@ const typescript_1 = __importDefault(require("typescript"));
9
9
  const form_template_1 = require("./angular/form.template");
10
10
  const list_template_1 = require("./angular/list.template");
11
11
  const crud_templates_1 = require("./nest/crud.templates");
12
- const microservice_templates_1 = require("./nest/microservice.templates");
13
12
  const migration_template_1 = require("./nest/migration.template");
14
13
  const relationships_1 = require("./shared/relationships");
15
14
  const sort_entities_1 = require("../migrations/sort-entities");
@@ -173,10 +172,7 @@ function assertValidTypeScript(source) {
173
172
  });
174
173
  (0, node_test_1.default)('kebab-case entities generate valid controller identifiers', () => {
175
174
  const controller = (0, crud_templates_1.nestControllerTemplate)('item-pedido');
176
- const msController = (0, microservice_templates_1.nestMsControllerTemplate)('item-pedido');
177
175
  strict_1.default.match(controller, /itemPedidoService: ItemPedidoService/);
178
176
  strict_1.default.doesNotMatch(controller, /item-pedidoService/);
179
- strict_1.default.match(msController, /this\.itemPedidoService\.findMany/);
180
177
  assertValidTypeScript(controller);
181
- assertValidTypeScript(msController);
182
178
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devstroupe/devkit-cli",
3
- "version": "1.1.15",
3
+ "version": "1.2.0-beta.1",
4
4
  "description": "DevsTroupe Development Kit CLI — scaffold NestJS+Angular projects, inject Spartan UI, generate CRUDs and audit architectural governance",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "commander": "^12.0.0",
38
38
  "fs-extra": "^11.2.0",
39
- "@devstroupe/devkit-core": "1.1.15"
39
+ "@devstroupe/devkit-core": "1.2.0-beta.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/fs-extra": "^11.0.4",
@@ -44,7 +44,7 @@
44
44
  "typescript": "^5.4.5"
45
45
  },
46
46
  "scripts": {
47
- "build": "tsc && node postbuild.js && chmod +x dist/index.js",
48
- "test": "tsc && node --test dist/rules/linter-rules.test.js dist/templates/relationship-templates.test.js"
47
+ "build": "node clean.js && tsc && node postbuild.js && chmod +x dist/index.js",
48
+ "test": "node clean.js && tsc && node postbuild.js && node --test dist/rules/linter-rules.test.js dist/templates/relationship-templates.test.js dist/templates/generator-characterization.test.js dist/generation/backend-layout.test.js dist/generation/backend-generator.integration.test.js dist/migrations/entity-module-move.test.js"
49
49
  }
50
50
  }
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/acorn@8.17.0/node_modules/acorn/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/acorn@8.17.0/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/acorn@8.17.0/node_modules/acorn/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/acorn@8.17.0/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/acorn@8.17.0/node_modules/acorn/bin/acorn" "$@"
19
- else
20
- exec node "$basedir/../../../../node_modules/.pnpm/acorn@8.17.0/node_modules/acorn/bin/acorn" "$@"
21
- fi