feat(architecture): enable enforce-module-boundaries with comprehensive rules

- Enable @nx/enforce-module-boundaries ESLint rule
- Add type-based constraints (feature, data-access, ui, shared, util, core, common)
- Add domain-based constraints (oms, crm, remission, checkout, availability, etc.)
- Prevent feature->feature, data-access->data-access dependencies
- Enforce domain isolation (no cross-domain imports)
- Tag all 80+ libraries with scope and type tags
- Create automated tagging script for new libraries
- Configure isa-app violations to be ignored

Rules enforce:
- Feature can import: data-access, ui, shared, util, core, common, icons
- Data-access can import: util, generated, common, core only
- UI can import: util, core, icons only
- Cross-domain imports forbidden (except shared, core, common, ui, utils, icons)
- Generated APIs only importable by data-access libraries
This commit is contained in:
Lorenz Hilpert
2025-11-20 17:55:04 +01:00
parent 664f42be08
commit c4480ca8d5
14 changed files with 552 additions and 42 deletions

163
scripts/add-library-tags.js Normal file
View File

@@ -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);