diff --git a/apps/isa-app/project.json b/apps/isa-app/project.json index 40798e1ca..3a60211ac 100644 --- a/apps/isa-app/project.json +++ b/apps/isa-app/project.json @@ -4,7 +4,7 @@ "projectType": "application", "prefix": "app", "sourceRoot": "apps/isa-app/src", - "tags": ["skip:ci"], + "tags": ["skip:ci", "scope:app", "type:app"], "targets": { "build": { "executor": "@angular-devkit/build-angular:application", diff --git a/eslint.config.js b/eslint.config.js index e8bb59ab8..c640653ce 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -13,24 +13,274 @@ module.exports = [ '**/generated/**', ], }, - // { - // files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], - // rules: { - // '@nx/enforce-module-boundaries': [ - // 'error', - // { - // enforceBuildableLibDependency: true, - // allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'], - // depConstraints: [ - // { - // sourceTag: '*', - // onlyDependOnLibsWithTags: ['*'], - // }, - // ], - // }, - // ], - // }, - // }, + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + rules: { + '@nx/enforce-module-boundaries': [ + 'error', + { + enforceBuildableLibDependency: true, + allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'], + depConstraints: [ + // ======================================== + // TYPE-BASED CONSTRAINTS (Layer Rules) + // ======================================== + + // FEATURE libraries can import: data-access, ui, shared, util, core (within same domain) + { + sourceTag: 'type:feature', + onlyDependOnLibsWithTags: [ + 'type:data-access', + 'type:ui', + 'type:shared', + 'type:util', + 'type:core', + 'type:common', + 'type:icon', + ], + }, + + // DATA-ACCESS libraries can import: util, generated, common-data-access only + { + sourceTag: 'type:data-access', + onlyDependOnLibsWithTags: [ + 'type:util', + 'type:generated', + 'type:common', + 'type:core', + ], + bannedExternalImports: [], + }, + + // DATA-ACCESS cannot import other DATA-ACCESS (except common) + { + sourceTag: 'type:data-access', + notDependOnLibsWithTags: ['type:data-access'], + allowedExternalImports: [], + }, + + // UI libraries can import: util, core only (NOT shared, NOT data-access) + { + sourceTag: 'type:ui', + onlyDependOnLibsWithTags: ['type:util', 'type:core', 'type:icon'], + }, + + // SHARED libraries can import: ui, util, core within same domain + { + sourceTag: 'type:shared', + onlyDependOnLibsWithTags: [ + 'type:ui', + 'type:util', + 'type:core', + 'type:common', + 'type:icon', + ], + }, + + // UTIL libraries are leaf nodes - cannot import feature/data-access/ui/shared + { + sourceTag: 'type:util', + onlyDependOnLibsWithTags: ['type:util', 'type:core'], + }, + + // GENERATED (swagger clients) can only be imported by data-access + // This is enforced by the data-access rule above + + // CORE libraries can be imported by anyone, but cannot import feature/data-access + { + sourceTag: 'type:core', + onlyDependOnLibsWithTags: ['type:core', 'type:util', 'type:common'], + }, + + // COMMON libraries can be imported by anyone + { + sourceTag: 'type:common', + onlyDependOnLibsWithTags: [ + 'type:util', + 'type:core', + 'type:generated', + ], + }, + + // ICON libraries can be imported by anyone + { + sourceTag: 'type:icon', + onlyDependOnLibsWithTags: [], + }, + + // ======================================== + // DOMAIN-BASED CONSTRAINTS (Scope Rules) + // ======================================== + + // OMS domain can only import from OMS, shared, core, common, ui, utils, icons + { + sourceTag: 'scope:oms', + onlyDependOnLibsWithTags: [ + 'scope:oms', + 'scope:shared', + 'scope:core', + 'scope:common', + 'scope:ui', + 'scope:utils', + 'scope:icons', + 'scope:generated', + ], + }, + + // CRM domain can only import from CRM, shared, core, common, ui, utils, icons + { + sourceTag: 'scope:crm', + onlyDependOnLibsWithTags: [ + 'scope:crm', + 'scope:shared', + 'scope:core', + 'scope:common', + 'scope:ui', + 'scope:utils', + 'scope:icons', + 'scope:generated', + ], + }, + + // REMISSION domain can only import from REMISSION, shared, core, common, ui, utils, icons + { + sourceTag: 'scope:remission', + onlyDependOnLibsWithTags: [ + 'scope:remission', + 'scope:shared', + 'scope:core', + 'scope:common', + 'scope:ui', + 'scope:utils', + 'scope:icons', + 'scope:generated', + ], + }, + + // CHECKOUT domain can only import from CHECKOUT, shared, core, common, ui, utils, icons + { + sourceTag: 'scope:checkout', + onlyDependOnLibsWithTags: [ + 'scope:checkout', + 'scope:shared', + 'scope:core', + 'scope:common', + 'scope:ui', + 'scope:utils', + 'scope:icons', + 'scope:generated', + ], + }, + + // AVAILABILITY domain can only import from AVAILABILITY, shared, core, common, ui, utils, icons + { + sourceTag: 'scope:availability', + onlyDependOnLibsWithTags: [ + 'scope:availability', + 'scope:shared', + 'scope:core', + 'scope:common', + 'scope:ui', + 'scope:utils', + 'scope:icons', + 'scope:generated', + ], + }, + + // CATALOGUE domain can only import from CATALOGUE, shared, core, common, ui, utils, icons + { + sourceTag: 'scope:catalogue', + onlyDependOnLibsWithTags: [ + 'scope:catalogue', + 'scope:shared', + 'scope:core', + 'scope:common', + 'scope:ui', + 'scope:utils', + 'scope:icons', + 'scope:generated', + ], + }, + + // SHARED libraries can be imported by all domains + { + sourceTag: 'scope:shared', + onlyDependOnLibsWithTags: [ + 'scope:shared', + 'scope:core', + 'scope:common', + 'scope:ui', + 'scope:utils', + 'scope:icons', + ], + }, + + // UI libraries can be imported by all domains + { + sourceTag: 'scope:ui', + onlyDependOnLibsWithTags: [ + 'scope:ui', + 'scope:core', + 'scope:utils', + 'scope:icons', + ], + }, + + // UTILS libraries can be imported by all domains + { + sourceTag: 'scope:utils', + onlyDependOnLibsWithTags: ['scope:utils', 'scope:core'], + }, + + // CORE libraries can be imported by all domains + { + sourceTag: 'scope:core', + onlyDependOnLibsWithTags: ['scope:core', 'scope:common', 'scope:utils'], + }, + + // COMMON libraries can be imported by all domains + { + sourceTag: 'scope:common', + onlyDependOnLibsWithTags: [ + 'scope:common', + 'scope:core', + 'scope:utils', + 'scope:generated', + ], + }, + + // ICONS libraries can be imported by all domains + { + sourceTag: 'scope:icons', + onlyDependOnLibsWithTags: [], + }, + + // GENERATED (swagger) can only be imported by data-access + { + sourceTag: 'scope:generated', + onlyDependOnLibsWithTags: [], + }, + + // ======================================== + // SPECIAL RULES + // ======================================== + + // APP scope - violations ignored for now (as per user requirement) + { + sourceTag: 'scope:app', + onlyDependOnLibsWithTags: ['*'], + }, + + // Disallow relative imports - must use path aliases + { + sourceTag: '*', + bannedExternalImports: ['../*', '../../*', '../../../*'], + }, + ], + }, + ], + }, + }, { files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], }, diff --git a/generated/swagger/availability-api/project.json b/generated/swagger/availability-api/project.json index 80ccad89d..939494c48 100644 --- a/generated/swagger/availability-api/project.json +++ b/generated/swagger/availability-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/availability-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger","availability","api"], + "tags": [ + "generated", + "swagger", + "availability", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/cat-search-api/project.json b/generated/swagger/cat-search-api/project.json index ee94d8320..7f802c20e 100644 --- a/generated/swagger/cat-search-api/project.json +++ b/generated/swagger/cat-search-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/cat-search-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger", "cat-search", "api"], + "tags": [ + "generated", + "swagger", + "cat-search", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/checkout-api/project.json b/generated/swagger/checkout-api/project.json index 8eb2e5a8d..6e6a8754d 100644 --- a/generated/swagger/checkout-api/project.json +++ b/generated/swagger/checkout-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/checkout-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger", "checkout", "api"], + "tags": [ + "generated", + "swagger", + "checkout", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/crm-api/project.json b/generated/swagger/crm-api/project.json index 62b607b2c..3647ab0d2 100644 --- a/generated/swagger/crm-api/project.json +++ b/generated/swagger/crm-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/crm-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger", "crm", "api"], + "tags": [ + "generated", + "swagger", + "crm", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/eis-api/project.json b/generated/swagger/eis-api/project.json index cd7813e73..67c75be7b 100644 --- a/generated/swagger/eis-api/project.json +++ b/generated/swagger/eis-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/eis-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger","eis", "api"], + "tags": [ + "generated", + "swagger", + "eis", + "api", + "scope:generated", + "type:generated" + ], "targets": { "download": { "command": "curl -o {projectRoot}/swagger.json https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1/swagger.json" @@ -17,8 +24,12 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], - "dependsOn": ["download"], + "outputs": [ + "{projectRoot}/src" + ], + "dependsOn": [ + "download" + ], "cache": false } } diff --git a/generated/swagger/inventory-api/project.json b/generated/swagger/inventory-api/project.json index 0eca7242b..cfbb98b0e 100644 --- a/generated/swagger/inventory-api/project.json +++ b/generated/swagger/inventory-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/inventory-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated", "swagger", "inventory", "api"], + "tags": [ + "generated", + "swagger", + "inventory", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/isa-api/project.json b/generated/swagger/isa-api/project.json index bf0130f75..553c72744 100644 --- a/generated/swagger/isa-api/project.json +++ b/generated/swagger/isa-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/isa-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger", "isa", "api"], + "tags": [ + "generated", + "swagger", + "isa", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/oms-api/project.json b/generated/swagger/oms-api/project.json index 6cbf5db28..bf1b3af83 100644 --- a/generated/swagger/oms-api/project.json +++ b/generated/swagger/oms-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/oms-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger", "oms", "api"], + "tags": [ + "generated", + "swagger", + "oms", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/print-api/project.json b/generated/swagger/print-api/project.json index b4ab0721d..25e345bbb 100644 --- a/generated/swagger/print-api/project.json +++ b/generated/swagger/print-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/print-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger", "print", "api"], + "tags": [ + "generated", + "swagger", + "print", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/generated/swagger/wws-api/project.json b/generated/swagger/wws-api/project.json index 286594e88..f1dd0ec6c 100644 --- a/generated/swagger/wws-api/project.json +++ b/generated/swagger/wws-api/project.json @@ -4,7 +4,14 @@ "sourceRoot": "generated/swagger/wws-api/src", "prefix": "lib", "projectType": "library", - "tags": ["generated","swagger", "wws", "api"], + "tags": [ + "generated", + "swagger", + "wws", + "api", + "scope:generated", + "type:generated" + ], "targets": { "generate": { "command": "ng-swagger-gen --config {projectRoot}/ng-swagger-gen.json --output {projectRoot}/src", @@ -13,7 +20,9 @@ "{projectRoot}/ng-swagger-gen.json", "!{projectRoot}/src/**/*.ts" ], - "outputs": ["{projectRoot}/src"], + "outputs": [ + "{projectRoot}/src" + ], "cache": false } } diff --git a/libs/icons/project.json b/libs/icons/project.json index 9daa4080b..d19b0c831 100644 --- a/libs/icons/project.json +++ b/libs/icons/project.json @@ -3,11 +3,16 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "libs/icons/src", "projectType": "library", - "tags": [], + "tags": [ + "scope:icons", + "type:icon" + ], "targets": { "test": { "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "outputs": [ + "{workspaceRoot}/coverage/{projectRoot}" + ], "options": { "jestConfig": "libs/icons/jest.config.ts" } diff --git a/scripts/add-library-tags.js b/scripts/add-library-tags.js new file mode 100644 index 000000000..d1ca00622 --- /dev/null +++ b/scripts/add-library-tags.js @@ -0,0 +1,163 @@ +#!/usr/bin/env node + +/** + * Script to add appropriate tags to all library project.json files + * for @nx/enforce-module-boundaries rule + */ + +const fs = require('fs'); +const path = require('path'); +const { glob } = require('glob'); + +/** + * Determine tags based on library path + * @param {string} projectPath - Path to the project.json file + * @returns {string[]} Array of tags + */ +function determineTags(projectPath) { + const tags = []; + + // Handle generated projects + if (projectPath.includes('generated/')) { + tags.push('scope:generated'); + tags.push('type:generated'); + return tags; + } + + const relativePath = projectPath.replace(/^.*?libs\//, 'libs/'); + + // Extract scope (domain) from path + const pathParts = relativePath.split('/'); + if (pathParts.length < 2) return tags; + + const domain = pathParts[1]; // e.g., 'oms', 'crm', 'ui', etc. + + // Add scope tag + tags.push(`scope:${domain}`); + + // Determine type tag based on path pattern + if (pathParts.length >= 3) { + const typeSegment = pathParts[2]; + + if (typeSegment === 'feature') { + tags.push('type:feature'); + } else if (typeSegment === 'data-access') { + tags.push('type:data-access'); + } else if (typeSegment === 'shared') { + tags.push('type:shared'); + } else if (typeSegment === 'utils' || typeSegment === 'util') { + tags.push('type:util'); + } else if (domain === 'ui') { + tags.push('type:ui'); + } else if (domain === 'core') { + tags.push('type:core'); + } else if (domain === 'common') { + tags.push('type:common'); + } else if (domain === 'icons') { + tags.push('type:icon'); + } else if (domain === 'utils') { + tags.push('type:util'); + } else if (domain === 'shared') { + // Libraries directly under libs/shared/* are shared components + tags.push('type:shared'); + } + } else { + // Top-level libraries (no subdirectory) + if (domain === 'ui') { + tags.push('type:ui'); + } else if (domain === 'core') { + tags.push('type:core'); + } else if (domain === 'common') { + tags.push('type:common'); + } else if (domain === 'icons') { + tags.push('type:icon'); + } else if (domain === 'utils') { + tags.push('type:util'); + } else if (domain === 'shared') { + tags.push('type:shared'); + } + } + + return tags; +} + +/** + * Update a project.json file with appropriate tags + * @param {string} projectPath - Path to the project.json file + */ +function updateProjectTags(projectPath) { + try { + const content = fs.readFileSync(projectPath, 'utf8'); + const project = JSON.parse(content); + + // Determine new tags + const newTags = determineTags(projectPath); + + if (newTags.length === 0) { + console.warn(`āš ļø No tags determined for ${projectPath}`); + return; + } + + // Preserve existing non-architectural tags (like skip:ci) + const existingTags = project.tags || []; + const preservedTags = existingTags.filter( + tag => !tag.startsWith('scope:') && !tag.startsWith('type:') + ); + + // Combine tags (preserved + new) + project.tags = [...new Set([...preservedTags, ...newTags])]; + + // Write back to file + fs.writeFileSync( + projectPath, + JSON.stringify(project, null, 2) + '\n', + 'utf8' + ); + + console.log(`āœ… ${projectPath}`); + console.log(` Tags: ${project.tags.join(', ')}`); + } catch (error) { + console.error(`āŒ Error processing ${projectPath}:`, error.message); + } +} + +/** + * Main execution + */ +async function main() { + console.log('šŸ” Finding all library project.json files...\n'); + + // Find all project.json files in libs directory + const libProjectFiles = await glob('libs/**/project.json', { + ignore: ['**/node_modules/**'], + cwd: path.resolve(__dirname, '..'), + absolute: true, + }); + + // Find all project.json files in generated directory + const generatedProjectFiles = await glob('generated/**/project.json', { + ignore: ['**/node_modules/**'], + cwd: path.resolve(__dirname, '..'), + absolute: true, + }); + + const projectFiles = [...libProjectFiles, ...generatedProjectFiles]; + + console.log(`Found ${libProjectFiles.length} library projects`); + console.log(`Found ${generatedProjectFiles.length} generated API projects`); + console.log(`Total: ${projectFiles.length} projects\n`); + + // Process each project.json + for (const projectFile of projectFiles) { + updateProjectTags(projectFile); + } + + console.log('\n✨ Done! All library tags have been updated.'); + console.log('\nšŸ“ Next steps:'); + console.log(' 1. Review the changes: git diff'); + console.log(' 2. Run lint to check for violations: npx nx run-many --target=lint --all'); + console.log(' 3. Commit the changes if everything looks good'); +} + +// Run the script +main().catch(console.error);