@devstroupe/devkit-cli 1.1.16 → 1.2.0-beta.2

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 +251 -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 +389 -210
  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 +20 -13
  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;
@@ -71,7 +71,7 @@ export interface I${pascalName}Repository {
71
71
  }
72
72
  `;
73
73
  }
74
- function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, indexes, persistence, automaticFields) {
74
+ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, indexes, persistence, automaticFields, relationImportPrefix = '../../../') {
75
75
  const pascalName = (0, names_1.kebabToPascal)(name);
76
76
  const relationships = (0, relationships_1.getRelationships)(properties);
77
77
  const typeormImports = ['Entity', 'PrimaryGeneratedColumn', 'Column'];
@@ -89,8 +89,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
89
89
  typeormImports.push('JoinTable');
90
90
  if ((indexes && indexes.length > 0) || properties.some((p) => p.index))
91
91
  typeormImports.push('Index');
92
- if (persistence?.deletion === 'soft-delete')
93
- typeormImports.push('DeleteDateColumn');
92
+ typeormImports.push('DeleteDateColumn');
94
93
  if (automaticFields?.timestamps)
95
94
  typeormImports.push('CreateDateColumn', 'UpdateDateColumn');
96
95
  if (automaticFields?.optimisticLocking)
@@ -98,7 +97,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
98
97
  const relImports = [...new Set(relationships.map((relation) => relation.target))]
99
98
  .map((targetKebab) => {
100
99
  const targetPascal = (0, names_1.kebabToPascal)(targetKebab);
101
- 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';`;
102
101
  })
103
102
  .join('\n');
104
103
  const propsCode = properties
@@ -184,8 +183,7 @@ function nestOrmEntityTemplate(name, properties, tableName, isTenantScoped, inde
184
183
  .join('\n\n');
185
184
  const tenantField = isTenantScoped ? '\n\n @Column()\n tenantId!: string;' : '';
186
185
  const relImportsStr = relImports ? `\n${relImports}` : '';
187
- const isSoftDelete = persistence?.deletion === 'soft-delete';
188
- 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;';
189
187
  let autoFieldsStr = '';
190
188
  if (automaticFields?.timestamps) {
191
189
  autoFieldsStr += '\n\n @CreateDateColumn({ name: \'created_at\' })\n createdAt!: Date;\n\n @UpdateDateColumn({ name: \'updated_at\' })\n updatedAt!: Date;';
@@ -223,9 +221,9 @@ ${propsCode}${tenantField}${softDeleteField}${autoFieldsStr}
223
221
  }
224
222
  function nestRepositoryAdapterTemplate(name, properties, isTenantScoped) {
225
223
  const pascalName = (0, names_1.kebabToPascal)(name);
226
- const tenantInject = isTenantScoped ? `import { Inject, Scope } from '@nestjs/common';\nimport { REQUEST } from '@nestjs/core';\n` : '';
227
- const tenantConstructor = isTenantScoped ? `,\n @Inject(REQUEST) private readonly request: any` : '';
228
- 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` : '';
229
227
  const relationships = (0, relationships_1.getRelationships)(properties);
230
228
  const relKeys = relationships
231
229
  .filter((relation) => relation.kind !== 'one-to-many')
@@ -241,7 +239,7 @@ function nestRepositoryAdapterTemplate(name, properties, isTenantScoped) {
241
239
  const relation = (0, relationships_1.getRelationships)([property])[0];
242
240
  return (0, relationships_1.isSingularRelationship)(relation.kind) ? [relation.foreignKey] : [];
243
241
  });
244
- const relationsOption = relKeys.length > 0 ? `, relations: [${relKeys.map(r => `'${r}'`).join(', ')}]` : '';
242
+ const relationsOption = relKeys.length > 0 ? `, relations: { ${relKeys.map(r => `${r}: true`).join(', ')} }` : '';
245
243
  const joinsCode = relKeys.map((r) => `\n qb.leftJoinAndSelect(\`\${alias}.${r}\`, '${r}');`).join('');
246
244
  const mapRels = relKeys.map((r) => ` if (orm.${r}) domain.${r} = orm.${r};`).join('\n');
247
245
  const mapToDomainProperties = properties
@@ -273,7 +271,7 @@ import { ${pascalName}OrmEntity } from './${name}.orm-entity';
273
271
  import { ${pascalName} } from '../../domain/${name}';
274
272
  import { I${pascalName}Repository } from '../../domain/${name}.repository.port';
275
273
  ${tenantInject}
276
- @Injectable(${isTenantScoped ? '{ scope: Scope.REQUEST }' : ''})
274
+ @Injectable()
277
275
  export class ${pascalName}RepositoryAdapter extends BaseRepository<${pascalName}OrmEntity> implements I${pascalName}Repository {
278
276
  constructor(
279
277
  @InjectRepository(${pascalName}OrmEntity)
@@ -325,7 +323,10 @@ export class ${pascalName}RepositoryAdapter extends BaseRepository<${pascalName}
325
323
  };
326
324
  }
327
325
 
328
- 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();
329
330
  }
330
331
 
331
332
  private mapToDomain(orm: ${pascalName}OrmEntity): ${pascalName} {
@@ -456,8 +457,14 @@ export class Filter${pascalName}Dto {
456
457
  @ApiProperty({ required: false })
457
458
  direction?: 'ASC' | 'DESC';
458
459
 
460
+ @ApiProperty({ required: false })
461
+ cursor?: string;
462
+
463
+ @ApiProperty({ required: false })
464
+ includeTotal?: boolean;
465
+
459
466
  @ApiProperty({ required: false, type: Object })
460
- filter?: Record<string, any>;
467
+ filter?: Record<string, unknown>;
461
468
  }
462
469
  `;
463
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.16",
3
+ "version": "1.2.0-beta.2",
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.16"
39
+ "@devstroupe/devkit-core": "1.2.0-beta.2"
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
@@ -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/browserslist@4.28.4/node_modules/browserslist/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/browserslist@4.28.4/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/browserslist@4.28.4/node_modules/browserslist/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/browserslist@4.28.4/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/browserslist@4.28.4/node_modules/browserslist/cli.js" "$@"
19
- else
20
- exec node "$basedir/../../../../node_modules/.pnpm/browserslist@4.28.4/node_modules/browserslist/cli.js" "$@"
21
- fi
@@ -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/eslint@10.6.0_jiti@2.7.0/node_modules/eslint/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/eslint@10.6.0_jiti@2.7.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/eslint@10.6.0_jiti@2.7.0/node_modules/eslint/node_modules:/home/vitor/trabalho/devstroupe/framework/node_modules/.pnpm/eslint@10.6.0_jiti@2.7.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/eslint@10.6.0_jiti@2.7.0/node_modules/eslint/bin/eslint.js" "$@"
19
- else
20
- exec node "$basedir/../../../../node_modules/.pnpm/eslint@10.6.0_jiti@2.7.0/node_modules/eslint/bin/eslint.js" "$@"
21
- fi