@devstroupe/devkit-cli 1.1.16 → 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.
- package/README.md +18 -0
- package/dist/boilerplates/angular-template/src/app/core/services/auth.service.ts +1 -1
- package/dist/boilerplates/angular-template/src/app/services/role.service.ts +1 -1
- package/dist/boilerplates/angular-template/src/app/services/user.service.ts +1 -1
- package/dist/boilerplates/nest-template/.dockerignore +8 -0
- package/dist/boilerplates/nest-template/package.json +13 -15
- package/dist/boilerplates/nest-template/src/app.module.ts +7 -10
- package/dist/boilerplates/nest-template/src/database/data-source.ts +4 -1
- package/dist/boilerplates/nest-template/src/database/migrations/1600000000000-create-roles.ts +2 -1
- package/dist/boilerplates/nest-template/src/database/migrations/1700000000000-create-users.ts +2 -2
- package/dist/boilerplates/nest-template/src/modules/auth/auth.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/auth/auth.module.ts +2 -7
- package/dist/boilerplates/nest-template/src/modules/auth/auth.service.ts +13 -11
- package/dist/boilerplates/nest-template/src/modules/auth/jwt-auth.guard.ts +5 -6
- package/dist/boilerplates/nest-template/src/modules/core/core.module.ts +9 -0
- package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.orm-entity.ts +4 -1
- package/dist/boilerplates/nest-template/src/modules/role/infra/database/role.repository.adapter.ts +2 -2
- package/dist/boilerplates/nest-template/src/modules/role/infra/http/role.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/storage/infra/http/storage.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.orm-entity.ts +4 -1
- package/dist/boilerplates/nest-template/src/modules/user/infra/database/user.repository.adapter.ts +2 -2
- package/dist/boilerplates/nest-template/src/modules/user/infra/http/user.controller.ts +1 -1
- package/dist/boilerplates/nest-template/src/modules/user/service/user.service.ts +6 -3
- package/dist/boilerplates/nest-template/src/platform/security/security.module.ts +20 -0
- package/dist/boilerplates/nest-template/src/platform/security/security.tokens.ts +4 -0
- package/dist/boilerplates/nest-template/src/platform/security/token-verification.module.ts +22 -0
- package/dist/boilerplates/nest-template/tsconfig.json +1 -1
- package/dist/generation/backend-generator.integration.test.d.ts +1 -0
- package/dist/generation/backend-generator.integration.test.js +232 -0
- package/dist/generation/backend-layout.d.ts +15 -0
- package/dist/generation/backend-layout.js +77 -0
- package/dist/generation/backend-layout.test.d.ts +1 -0
- package/dist/generation/backend-layout.test.js +48 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +364 -205
- package/dist/migrations/entity-module-move.d.ts +16 -0
- package/dist/migrations/entity-module-move.js +98 -0
- package/dist/migrations/entity-module-move.test.d.ts +1 -0
- package/dist/migrations/entity-module-move.test.js +30 -0
- package/dist/rules/linter-rules.js +252 -22
- package/dist/rules/linter-rules.test.js +125 -0
- package/dist/templates/angular/service.template.d.ts +1 -1
- package/dist/templates/angular/service.template.js +2 -2
- package/dist/templates/generator-characterization.test.d.ts +1 -0
- package/dist/templates/generator-characterization.test.js +42 -0
- package/dist/templates/nest/crud.templates.d.ts +3 -3
- package/dist/templates/nest/crud.templates.js +20 -13
- package/dist/templates/nest/functional-module.templates.d.ts +10 -0
- package/dist/templates/nest/functional-module.templates.js +261 -0
- package/dist/templates/nest/index.d.ts +1 -1
- package/dist/templates/nest/index.js +1 -1
- package/dist/templates/nest/migration.template.js +6 -3
- package/dist/templates/relationship-templates.test.js +0 -4
- package/package.json +4 -4
- package/dist/boilerplates/angular-template/node_modules/.bin/acorn +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/browserslist +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/eslint +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/jiti +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/js-yaml +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/lessc +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/ng +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/ng-xi18n +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/ngc +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/nx +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/nx-cloud +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/sass +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/terser +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/tsc +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/tsserver +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/vite +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/vitest +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/webpack +0 -21
- package/dist/boilerplates/angular-template/node_modules/.bin/yaml +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/acorn +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/nest +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/prettier +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-cwd +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-esm +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-script +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-node-transpile-only +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/ts-script +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/tsc +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/tsserver +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-commonjs +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/typeorm-ts-node-esm +0 -21
- package/dist/boilerplates/nest-template/node_modules/.bin/webpack +0 -21
- package/dist/templates/nest/microservice.templates.d.ts +0 -4
- package/dist/templates/nest/microservice.templates.js +0 -157
|
@@ -0,0 +1,232 @@
|
|
|
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_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
11
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
+
const index_1 = require("../index");
|
|
13
|
+
const linter_rules_1 = require("../rules/linter-rules");
|
|
14
|
+
const repositoryRoot = node_path_1.default.resolve(__dirname, '../../../..');
|
|
15
|
+
const nestTemplate = node_path_1.default.join(repositoryRoot, 'boilerplates', 'nest-template');
|
|
16
|
+
const entities = [
|
|
17
|
+
{ name: 'account', properties: [], crud: { generateFrontend: false } },
|
|
18
|
+
{ name: 'product', module: 'catalog', properties: [], crud: { generateFrontend: false } },
|
|
19
|
+
{
|
|
20
|
+
name: 'category', module: 'catalog', crud: { generateFrontend: false }, properties: [{
|
|
21
|
+
name: 'parentProductId', label: 'Parent product', type: 'relationship', required: false,
|
|
22
|
+
relation: { target: 'product', foreignKey: 'parentProductId', kind: 'many-to-one' },
|
|
23
|
+
}],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'tag', module: 'catalog', crud: { generateFrontend: false }, properties: [{
|
|
27
|
+
name: 'products', label: 'Products', type: 'relationship', required: false,
|
|
28
|
+
relation: { target: 'product', kind: 'many-to-many', control: 'multi-select' },
|
|
29
|
+
}],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'stock', module: 'inventory', crud: { generateFrontend: false }, properties: [{
|
|
33
|
+
name: 'productId', label: 'Product', type: 'relationship', required: true,
|
|
34
|
+
relation: { target: 'product', foreignKey: 'productId', kind: 'many-to-one' },
|
|
35
|
+
}],
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
function writeConfig(root, backendType) {
|
|
39
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(root, 'devstroupe.config.ts'), `module.exports = ${JSON.stringify({
|
|
40
|
+
projectName: 'fixture', backendType, backendPath: './backend', frontendPath: './frontend',
|
|
41
|
+
multiTenant: { enabled: true }, entities,
|
|
42
|
+
}, null, 2)};\n`);
|
|
43
|
+
}
|
|
44
|
+
function runGenerator(root) {
|
|
45
|
+
const previous = process.cwd();
|
|
46
|
+
process.chdir(root);
|
|
47
|
+
try {
|
|
48
|
+
index_1.program.parse(['node', 'devstroupe', 'generate', 'crud', 'all']);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
process.chdir(previous);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function applyModuleMove(root, entityName, moduleName) {
|
|
55
|
+
const previous = process.cwd();
|
|
56
|
+
process.chdir(root);
|
|
57
|
+
try {
|
|
58
|
+
index_1.program.parse(['node', 'devstroupe', 'entity:set-module', entityName, moduleName, '--apply']);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
process.chdir(previous);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function assertCompiles(projectRoot) {
|
|
65
|
+
const nodeModules = node_path_1.default.join(projectRoot, 'node_modules');
|
|
66
|
+
if (!node_fs_1.default.existsSync(nodeModules))
|
|
67
|
+
node_fs_1.default.symlinkSync(node_path_1.default.join(nestTemplate, 'node_modules'), nodeModules, 'dir');
|
|
68
|
+
const configPath = node_path_1.default.join(projectRoot, 'tsconfig.json');
|
|
69
|
+
const loaded = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
70
|
+
const parsed = typescript_1.default.parseJsonConfigFileContent(loaded.config, typescript_1.default.sys, projectRoot, {
|
|
71
|
+
noEmit: true, incremental: false,
|
|
72
|
+
}, configPath);
|
|
73
|
+
const diagnostics = typescript_1.default.getPreEmitDiagnostics(typescript_1.default.createProgram(parsed.fileNames, parsed.options));
|
|
74
|
+
strict_1.default.equal(diagnostics.length, 0, typescript_1.default.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
75
|
+
getCurrentDirectory: () => projectRoot,
|
|
76
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
77
|
+
getNewLine: () => '\n',
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
(0, node_test_1.default)('monolith generation groups entities and remains idempotent', () => {
|
|
81
|
+
const root = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'devkit-monolith-'));
|
|
82
|
+
const backend = node_path_1.default.join(root, 'backend');
|
|
83
|
+
node_fs_1.default.cpSync(nestTemplate, backend, {
|
|
84
|
+
recursive: true,
|
|
85
|
+
filter: (source) => !source.includes(`${node_path_1.default.sep}node_modules`) && !source.includes(`${node_path_1.default.sep}dist`),
|
|
86
|
+
});
|
|
87
|
+
writeConfig(root, 'monolith');
|
|
88
|
+
runGenerator(root);
|
|
89
|
+
runGenerator(root);
|
|
90
|
+
const catalogModule = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'src/modules/catalog/catalog.module.ts'), 'utf8');
|
|
91
|
+
const appModule = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'src/app.module.ts'), 'utf8');
|
|
92
|
+
const productController = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'src/modules/catalog/product/api/product.controller.ts'), 'utf8');
|
|
93
|
+
const categoryEntity = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'src/modules/catalog/category/infrastructure/persistence/typeorm/category.orm-entity.ts'), 'utf8');
|
|
94
|
+
const categoryRepository = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'src/modules/catalog/category/infrastructure/persistence/typeorm/category.repository.ts'), 'utf8');
|
|
95
|
+
const tagRepository = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'src/modules/catalog/tag/infrastructure/persistence/typeorm/tag.repository.ts'), 'utf8');
|
|
96
|
+
const stockEntity = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'src/modules/inventory/stock/infrastructure/persistence/typeorm/stock.orm-entity.ts'), 'utf8');
|
|
97
|
+
strict_1.default.match(catalogModule, /ProductOrmEntity/);
|
|
98
|
+
strict_1.default.match(catalogModule, /CategoryOrmEntity/);
|
|
99
|
+
strict_1.default.match(catalogModule, /TagOrmEntity/);
|
|
100
|
+
strict_1.default.match(productController, /@UseGuards\(JwtAuthGuard\)/);
|
|
101
|
+
strict_1.default.match(productController, /@ApiBearerAuth\(\)/);
|
|
102
|
+
strict_1.default.match(categoryEntity, /ProductOrmEntity/);
|
|
103
|
+
strict_1.default.match(categoryEntity, /@ManyToOne/);
|
|
104
|
+
strict_1.default.match(categoryRepository, /allowedFilters = \["parentProductId"\]/);
|
|
105
|
+
strict_1.default.match(categoryRepository, /allowedSorts = \["parentProductId"\]/);
|
|
106
|
+
strict_1.default.match(tagRepository, /allowedFilters = \[\]/);
|
|
107
|
+
strict_1.default.equal((appModule.match(/CatalogModule/g) ?? []).length, 2); // import + @Module entry
|
|
108
|
+
strict_1.default.doesNotMatch(stockEntity, /ProductOrmEntity/);
|
|
109
|
+
strict_1.default.match(stockEntity, /productId/);
|
|
110
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(backend, 'src/modules/core/core.module.ts')));
|
|
111
|
+
strict_1.default.ok(!node_fs_1.default.existsSync(node_path_1.default.join(backend, 'src/modules/product/product.module.ts')));
|
|
112
|
+
assertCompiles(backend);
|
|
113
|
+
strict_1.default.equal((0, linter_rules_1.runLinter)(root, {
|
|
114
|
+
backendPath: './backend', frontendPath: './frontend', multiTenant: { enabled: true }, entities,
|
|
115
|
+
}).violations.filter(({ severity }) => severity === 'error').length, 0);
|
|
116
|
+
applyModuleMove(root, 'account', 'accounts');
|
|
117
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(backend, 'src/modules/accounts/account')));
|
|
118
|
+
strict_1.default.ok(!node_fs_1.default.existsSync(node_path_1.default.join(backend, 'src/modules/core/account')));
|
|
119
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(root, 'devstroupe.config.ts.devstroupe-backup')));
|
|
120
|
+
strict_1.default.match(node_fs_1.default.readFileSync(node_path_1.default.join(root, 'devstroupe.config.ts'), 'utf8'), /"name": "account",\n\s+module: 'accounts',/);
|
|
121
|
+
assertCompiles(backend);
|
|
122
|
+
});
|
|
123
|
+
(0, node_test_1.default)('microservice generation creates one HTTP application, database and gateway route per module', () => {
|
|
124
|
+
const root = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'devkit-microservices-'));
|
|
125
|
+
writeConfig(root, 'microservices');
|
|
126
|
+
runGenerator(root);
|
|
127
|
+
runGenerator(root);
|
|
128
|
+
const backend = node_path_1.default.join(root, 'backend');
|
|
129
|
+
const compose = node_fs_1.default.readFileSync(node_path_1.default.join(root, 'docker-compose.yml'), 'utf8');
|
|
130
|
+
const initSql = node_fs_1.default.readFileSync(node_path_1.default.join(root, 'db-init/init.sql'), 'utf8');
|
|
131
|
+
const gatewayCatalog = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'api-gateway/src/modules/catalog/catalog.module.ts'), 'utf8');
|
|
132
|
+
const gatewayCatalogController = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'api-gateway/src/modules/catalog/catalog.gateway.controller.ts'), 'utf8');
|
|
133
|
+
const gatewayAppModule = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'api-gateway/src/app.module.ts'), 'utf8');
|
|
134
|
+
const catalogRoot = node_path_1.default.join(backend, 'apps/catalog-service');
|
|
135
|
+
const catalogAppModule = node_fs_1.default.readFileSync(node_path_1.default.join(catalogRoot, 'src/app.module.ts'), 'utf8');
|
|
136
|
+
const catalogMain = node_fs_1.default.readFileSync(node_path_1.default.join(catalogRoot, 'src/main.ts'), 'utf8');
|
|
137
|
+
const catalogDataSource = node_fs_1.default.readFileSync(node_path_1.default.join(catalogRoot, 'src/database/data-source.ts'), 'utf8');
|
|
138
|
+
const catalogProductHttp = node_fs_1.default.readFileSync(node_path_1.default.join(catalogRoot, 'src/modules/catalog/product/api/product.controller.ts'), 'utf8');
|
|
139
|
+
const catalogCategoryEntity = node_fs_1.default.readFileSync(node_path_1.default.join(catalogRoot, 'src/modules/catalog/category/infrastructure/persistence/typeorm/category.orm-entity.ts'), 'utf8');
|
|
140
|
+
const catalogDockerIgnore = node_fs_1.default.readFileSync(node_path_1.default.join(catalogRoot, '.dockerignore'), 'utf8');
|
|
141
|
+
const catalogPackage = node_fs_1.default.readFileSync(node_path_1.default.join(catalogRoot, 'package.json'), 'utf8');
|
|
142
|
+
const platformPackage = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'packages/platform/package.json'), 'utf8');
|
|
143
|
+
const catalogMigrations = node_fs_1.default.readdirSync(node_path_1.default.join(catalogRoot, 'src/database/migrations'));
|
|
144
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(backend, 'apps/catalog-service/src/modules/catalog/catalog.module.ts')));
|
|
145
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(backend, 'apps/core-service/src/modules/core/core.module.ts')));
|
|
146
|
+
strict_1.default.ok(!node_fs_1.default.existsSync(node_path_1.default.join(backend, 'apps/product-service')));
|
|
147
|
+
strict_1.default.match(gatewayCatalog, /ModuleHttpProxy/);
|
|
148
|
+
strict_1.default.match(gatewayCatalog, /CatalogGatewayController/);
|
|
149
|
+
strict_1.default.match(gatewayCatalogController, /@Controller\('catalog'\)/);
|
|
150
|
+
strict_1.default.match(gatewayCatalogController, /authenticateGatewayRequest/);
|
|
151
|
+
strict_1.default.match(gatewayCatalogController, /CATALOG_SERVICE_HOST/);
|
|
152
|
+
strict_1.default.ok(!node_fs_1.default.existsSync(node_path_1.default.join(backend, 'api-gateway/src/modules/catalog/product')));
|
|
153
|
+
strict_1.default.doesNotMatch(gatewayAppModule, /TypeOrmModule|UserModule|AuthModule|RoleModule/);
|
|
154
|
+
strict_1.default.doesNotMatch(gatewayAppModule, /,TokenVerificationModule/);
|
|
155
|
+
strict_1.default.ok(!node_fs_1.default.existsSync(node_path_1.default.join(backend, 'api-gateway/src/database')));
|
|
156
|
+
strict_1.default.match(compose, /devstroupe_catalog/);
|
|
157
|
+
strict_1.default.doesNotMatch(compose, /DB_NAME: devstroupe_gateway_identity/);
|
|
158
|
+
strict_1.default.doesNotMatch(initSql, /devstroupe_gateway_identity/);
|
|
159
|
+
strict_1.default.doesNotMatch(compose, /devstroupe_product/);
|
|
160
|
+
strict_1.default.doesNotMatch(compose, /"3001:3001"/);
|
|
161
|
+
strict_1.default.match(compose, /expose:\n\s+- "3001"/);
|
|
162
|
+
strict_1.default.equal((compose.match(/^ catalog-service:/gm) ?? []).length, 1);
|
|
163
|
+
strict_1.default.match(catalogAppModule, /migrationsRun: true/);
|
|
164
|
+
strict_1.default.match(catalogAppModule, /TokenVerificationModule/);
|
|
165
|
+
strict_1.default.match(catalogAppModule, /ExecutionContextInterceptor/);
|
|
166
|
+
strict_1.default.match(catalogMain, /NestFactory\.create\(AppModule\)/);
|
|
167
|
+
strict_1.default.doesNotMatch(catalogMain, /Transport\.TCP|createMicroservice/);
|
|
168
|
+
strict_1.default.match(catalogProductHttp, /@Controller\('catalog\/product'\)/);
|
|
169
|
+
strict_1.default.match(catalogProductHttp, /extends BaseCrudController/);
|
|
170
|
+
strict_1.default.match(catalogCategoryEntity, /ProductOrmEntity/);
|
|
171
|
+
strict_1.default.match(catalogDataSource, /infrastructure\/persistence\/typeorm/);
|
|
172
|
+
strict_1.default.ok(catalogMigrations.some((file) => file.endsWith('-create-product.ts')));
|
|
173
|
+
strict_1.default.ok(catalogMigrations.some((file) => file.endsWith('-create-category.ts')));
|
|
174
|
+
strict_1.default.ok(!catalogMigrations.some((file) => file.endsWith('-create-users.ts')));
|
|
175
|
+
strict_1.default.ok(!catalogMigrations.some((file) => file.endsWith('-create-roles.ts')));
|
|
176
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(catalogRoot, 'src/platform/security/token-verification.module.ts')));
|
|
177
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(backend, 'api-gateway/src/platform/security/token-verification.module.ts')));
|
|
178
|
+
const coreRoot = node_path_1.default.join(backend, 'apps/core-service');
|
|
179
|
+
const coreAppModule = node_fs_1.default.readFileSync(node_path_1.default.join(coreRoot, 'src/app.module.ts'), 'utf8');
|
|
180
|
+
const coreModule = node_fs_1.default.readFileSync(node_path_1.default.join(coreRoot, 'src/modules/core/core.module.ts'), 'utf8');
|
|
181
|
+
strict_1.default.match(coreAppModule, /SecurityModule/);
|
|
182
|
+
strict_1.default.match(coreModule, /AuthModule/);
|
|
183
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(coreRoot, 'src/modules/user/user.module.ts')));
|
|
184
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(coreRoot, 'src/modules/role/role.module.ts')));
|
|
185
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(coreRoot, 'src/modules/auth/auth.module.ts')));
|
|
186
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(coreRoot, 'src/database/migrations/1600000000000-create-roles.ts')));
|
|
187
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(coreRoot, 'src/database/migrations/1700000000000-create-users.ts')));
|
|
188
|
+
strict_1.default.match(catalogDockerIgnore, /^node_modules$/m);
|
|
189
|
+
strict_1.default.match(catalogDockerIgnore, /^local_packages\/\*\/node_modules$/m);
|
|
190
|
+
strict_1.default.match(platformPackage, /"name": "@fixture\/platform"/);
|
|
191
|
+
strict_1.default.match(catalogPackage, /"@fixture\/platform": "file:local_packages\/project-platform"/);
|
|
192
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(catalogRoot, 'local_packages/project-platform/src/integrations/index.js')));
|
|
193
|
+
strict_1.default.ok(node_fs_1.default.existsSync(node_path_1.default.join(backend, 'packages/platform/src/observability/README.md')));
|
|
194
|
+
strict_1.default.equal(node_fs_1.default.readdirSync(node_path_1.default.join(backend, 'packages/platform/src'), { recursive: true })
|
|
195
|
+
.some((entry) => String(entry).endsWith('.orm-entity.ts')), false);
|
|
196
|
+
assertCompiles(node_path_1.default.join(backend, 'api-gateway'));
|
|
197
|
+
for (const service of ['catalog-service', 'core-service', 'inventory-service']) {
|
|
198
|
+
assertCompiles(node_path_1.default.join(backend, 'apps', service));
|
|
199
|
+
}
|
|
200
|
+
strict_1.default.equal((0, linter_rules_1.runLinter)(root, {
|
|
201
|
+
backendType: 'microservices', backendPath: './backend', frontendPath: './frontend',
|
|
202
|
+
multiTenant: { enabled: true }, entities,
|
|
203
|
+
}).violations.filter(({ severity }) => severity === 'error').length, 0);
|
|
204
|
+
});
|
|
205
|
+
(0, node_test_1.default)('local package copies preserve runtime dependencies in Docker builds', () => {
|
|
206
|
+
const root = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'devkit-local-packages-'));
|
|
207
|
+
const backend = node_path_1.default.join(root, 'backend');
|
|
208
|
+
node_fs_1.default.mkdirSync(backend, { recursive: true });
|
|
209
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(backend, 'package.json'), JSON.stringify({
|
|
210
|
+
dependencies: {
|
|
211
|
+
'@devstroupe/devkit-core': 'workspace:*',
|
|
212
|
+
'@devstroupe/devkit-nest': 'workspace:*',
|
|
213
|
+
},
|
|
214
|
+
}));
|
|
215
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(backend, 'Dockerfile'), [
|
|
216
|
+
'FROM node:20-alpine',
|
|
217
|
+
'COPY package*.json ./',
|
|
218
|
+
'RUN npm install && rm -rf local_packages/*/node_modules',
|
|
219
|
+
'RUN npm install --only=production && rm -rf local_packages/*/node_modules',
|
|
220
|
+
].join('\n'));
|
|
221
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(backend, 'tsconfig.json'), JSON.stringify({ compilerOptions: {} }));
|
|
222
|
+
(0, index_1.patchLocalDependencies)(backend, null, node_path_1.default.join(repositoryRoot, 'packages/devkit-cli/dist/boilerplates'));
|
|
223
|
+
(0, index_1.patchLocalDependencies)(backend, null, node_path_1.default.join(repositoryRoot, 'packages/devkit-cli/dist/boilerplates'));
|
|
224
|
+
const localNestPackage = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'local_packages/devkit-nest/package.json'), 'utf8'));
|
|
225
|
+
const dockerfile = node_fs_1.default.readFileSync(node_path_1.default.join(backend, 'Dockerfile'), 'utf8');
|
|
226
|
+
strict_1.default.equal(localNestPackage.dependencies['@devstroupe/devkit-core'], 'file:../devkit-core');
|
|
227
|
+
strict_1.default.ok(localNestPackage.dependencies['@aws-sdk/client-s3']);
|
|
228
|
+
strict_1.default.ok(localNestPackage.peerDependencies['@nestjs/core']);
|
|
229
|
+
strict_1.default.match(dockerfile, /COPY local_packages\/ \.\/local_packages\//);
|
|
230
|
+
strict_1.default.equal((dockerfile.match(/COPY local_packages\/ \.\/local_packages\//g) ?? []).length, 1);
|
|
231
|
+
strict_1.default.doesNotMatch(dockerfile, /rm -rf local_packages/);
|
|
232
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IDevkitEntityConfig, IDevkitEntityModuleGroup, IDevkitEntityProperty } from '@devstroupe/devkit-core';
|
|
2
|
+
export interface BackendModuleGroup extends IDevkitEntityModuleGroup {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
databaseName: string;
|
|
5
|
+
port: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildBackendModuleGroups(entities: readonly IDevkitEntityConfig[], firstPort?: number): BackendModuleGroup[];
|
|
8
|
+
export declare function moduleForEntity(entity: IDevkitEntityConfig, entities: readonly IDevkitEntityConfig[]): BackendModuleGroup;
|
|
9
|
+
/**
|
|
10
|
+
* Cross-module relationships are persisted as identifiers only. This keeps a
|
|
11
|
+
* monolith module extractable and prevents cross-database TypeORM metadata.
|
|
12
|
+
*/
|
|
13
|
+
export declare function persistencePropertiesForEntity(entity: IDevkitEntityConfig, entities: readonly IDevkitEntityConfig[]): IDevkitEntityProperty[];
|
|
14
|
+
export declare function crossModuleRelationships(entity: IDevkitEntityConfig, entities: readonly IDevkitEntityConfig[]): string[];
|
|
15
|
+
export declare function validateModuleRelationships(entities: readonly IDevkitEntityConfig[]): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildBackendModuleGroups = buildBackendModuleGroups;
|
|
4
|
+
exports.moduleForEntity = moduleForEntity;
|
|
5
|
+
exports.persistencePropertiesForEntity = persistencePropertiesForEntity;
|
|
6
|
+
exports.crossModuleRelationships = crossModuleRelationships;
|
|
7
|
+
exports.validateModuleRelationships = validateModuleRelationships;
|
|
8
|
+
const devkit_core_1 = require("@devstroupe/devkit-core");
|
|
9
|
+
const relationships_1 = require("../templates/shared/relationships");
|
|
10
|
+
function buildBackendModuleGroups(entities, firstPort = 3001) {
|
|
11
|
+
const configured = (0, devkit_core_1.groupEntitiesByModule)(entities);
|
|
12
|
+
const core = configured.find((group) => group.name === 'core') ?? { name: 'core', entities: [] };
|
|
13
|
+
const groups = [core, ...configured.filter((group) => group.name !== 'core')];
|
|
14
|
+
return groups.map((group, index) => ({
|
|
15
|
+
...group,
|
|
16
|
+
serviceName: `${group.name}-service`,
|
|
17
|
+
databaseName: `devstroupe_${group.name.replace(/-/g, '_')}`,
|
|
18
|
+
port: firstPort + index,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
function moduleForEntity(entity, entities) {
|
|
22
|
+
const moduleName = (0, devkit_core_1.resolveEntityModule)(entity);
|
|
23
|
+
const group = buildBackendModuleGroups(entities).find((candidate) => candidate.name === moduleName);
|
|
24
|
+
if (!group)
|
|
25
|
+
throw new Error(`Functional module "${moduleName}" was not found.`);
|
|
26
|
+
return group;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Cross-module relationships are persisted as identifiers only. This keeps a
|
|
30
|
+
* monolith module extractable and prevents cross-database TypeORM metadata.
|
|
31
|
+
*/
|
|
32
|
+
function persistencePropertiesForEntity(entity, entities) {
|
|
33
|
+
const ownerModule = (0, devkit_core_1.resolveEntityModule)(entity);
|
|
34
|
+
return entity.properties.flatMap((property) => {
|
|
35
|
+
if (property.type !== 'relationship')
|
|
36
|
+
return [property];
|
|
37
|
+
const relationship = (0, relationships_1.normalizeRelationship)(property);
|
|
38
|
+
const target = entities.find((candidate) => candidate.name === relationship.target);
|
|
39
|
+
if (!target || (0, devkit_core_1.resolveEntityModule)(target) === ownerModule)
|
|
40
|
+
return [property];
|
|
41
|
+
if (!relationship.foreignKey)
|
|
42
|
+
return [];
|
|
43
|
+
return [{
|
|
44
|
+
name: relationship.foreignKey,
|
|
45
|
+
label: property.label,
|
|
46
|
+
type: 'number',
|
|
47
|
+
required: property.required,
|
|
48
|
+
index: true,
|
|
49
|
+
}];
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function crossModuleRelationships(entity, entities) {
|
|
53
|
+
const ownerModule = (0, devkit_core_1.resolveEntityModule)(entity);
|
|
54
|
+
return entity.properties
|
|
55
|
+
.filter((property) => property.type === 'relationship')
|
|
56
|
+
.map(relationships_1.normalizeRelationship)
|
|
57
|
+
.filter((relationship) => {
|
|
58
|
+
const target = entities.find((candidate) => candidate.name === relationship.target);
|
|
59
|
+
return target && (0, devkit_core_1.resolveEntityModule)(target) !== ownerModule;
|
|
60
|
+
})
|
|
61
|
+
.map((relationship) => relationship.property.name);
|
|
62
|
+
}
|
|
63
|
+
function validateModuleRelationships(entities) {
|
|
64
|
+
for (const entity of entities) {
|
|
65
|
+
const ownerModule = (0, devkit_core_1.resolveEntityModule)(entity);
|
|
66
|
+
for (const property of entity.properties.filter((candidate) => candidate.type === 'relationship')) {
|
|
67
|
+
const relationship = (0, relationships_1.normalizeRelationship)(property);
|
|
68
|
+
const target = entities.find((candidate) => candidate.name === relationship.target);
|
|
69
|
+
if (!target || (0, devkit_core_1.resolveEntityModule)(target) === ownerModule)
|
|
70
|
+
continue;
|
|
71
|
+
if (relationship.kind === 'one-to-many' || relationship.kind === 'many-to-many') {
|
|
72
|
+
throw new Error(`Cross-module ${relationship.kind} "${entity.name}.${property.name}" is not a TypeORM relation. `
|
|
73
|
+
+ 'Model the association as an explicit owned entity/CRUD with external IDs.');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
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 = __importDefault(require("node:test"));
|
|
8
|
+
const backend_layout_1 = require("./backend-layout");
|
|
9
|
+
const entities = [
|
|
10
|
+
{ name: 'product', module: 'catalog', properties: [] },
|
|
11
|
+
{ name: 'category', module: 'catalog', properties: [] },
|
|
12
|
+
{
|
|
13
|
+
name: 'stock', module: 'inventory', properties: [{
|
|
14
|
+
name: 'productId', label: 'Product', type: 'relationship', required: true,
|
|
15
|
+
relation: { target: 'product', kind: 'many-to-one', foreignKey: 'productId' },
|
|
16
|
+
}],
|
|
17
|
+
},
|
|
18
|
+
{ name: 'user', properties: [] },
|
|
19
|
+
];
|
|
20
|
+
(0, node_test_1.default)('backend groups expose stable service, database and port metadata', () => {
|
|
21
|
+
strict_1.default.deepEqual((0, backend_layout_1.buildBackendModuleGroups)(entities).map(({ name, serviceName, databaseName, port, entities: members }) => ({
|
|
22
|
+
name, serviceName, databaseName, port, members: members.map(({ name: entityName }) => entityName),
|
|
23
|
+
})), [
|
|
24
|
+
{ name: 'core', serviceName: 'core-service', databaseName: 'devstroupe_core', port: 3001, members: ['user'] },
|
|
25
|
+
{ name: 'catalog', serviceName: 'catalog-service', databaseName: 'devstroupe_catalog', port: 3002, members: ['category', 'product'] },
|
|
26
|
+
{ name: 'inventory', serviceName: 'inventory-service', databaseName: 'devstroupe_inventory', port: 3003, members: ['stock'] },
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
(0, node_test_1.default)('cross-module TypeORM relationships become scalar identifiers', () => {
|
|
30
|
+
const stock = entities.find(({ name }) => name === 'stock');
|
|
31
|
+
strict_1.default.deepEqual((0, backend_layout_1.persistencePropertiesForEntity)(stock, entities), [{
|
|
32
|
+
name: 'productId', label: 'Product', type: 'number', required: true, index: true,
|
|
33
|
+
}]);
|
|
34
|
+
});
|
|
35
|
+
(0, node_test_1.default)('same-module many-to-many stays local while cross-module collections require explicit ownership', () => {
|
|
36
|
+
const local = [
|
|
37
|
+
{ name: 'product', module: 'catalog', properties: [] },
|
|
38
|
+
{ name: 'tag', module: 'catalog', properties: [{
|
|
39
|
+
name: 'products', label: 'Products', type: 'relationship',
|
|
40
|
+
relation: { target: 'product', kind: 'many-to-many', control: 'multi-select' },
|
|
41
|
+
}] },
|
|
42
|
+
];
|
|
43
|
+
strict_1.default.doesNotThrow(() => (0, backend_layout_1.validateModuleRelationships)(local));
|
|
44
|
+
strict_1.default.throws(() => (0, backend_layout_1.validateModuleRelationships)([
|
|
45
|
+
local[0],
|
|
46
|
+
{ ...local[1], module: 'taxonomy' },
|
|
47
|
+
]), /explicit owned entity\/CRUD with external IDs/);
|
|
48
|
+
});
|
package/dist/index.d.ts
CHANGED