Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build: Detect circular dependencies with Madge #17924

Open
wants to merge 16 commits into
base: v15/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test-backoffice.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
cache: npm
cache-dependency-path: ./src/Umbraco.Web.UI.Client/package-lock.json
- run: npm ci --no-audit --no-fund --prefer-offline
- name: Check for circular dependencies
run: node devops/circular/index.js src
- run: npm run lint:errors
- run: npm run build:for:cms
- run: npm run check:paths
Expand Down
75 changes: 75 additions & 0 deletions src/Umbraco.Web.UI.Client/devops/circular/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* This module is used to detect circular dependencies in the Umbraco backoffice.
* It is used in the build process to ensure that we don't have circular dependencies.
* @example node devops/circular/index.js src
* @author Umbraco HQ
*/

import madge from 'madge';
import { join } from 'path';
import { mkdirSync } from 'fs';

const __dirname = import.meta.dirname;
const IS_GITHUB_ACTIONS = process.env.GITHUB_ACTIONS === 'true';
const IS_AZURE_PIPELINES = process.env.TF_BUILD === 'true';
const baseDir = process.argv[2] || 'src';
const specificPaths = (process.argv[3] || '').split(',');

console.log('Scanning for circular dependencies in:', baseDir);

const madgeSetup = await madge(specificPaths, {
baseDir,
fileExtensions: ['ts'],
tsConfig: join(baseDir, 'tsconfig.build.json'),
detectiveOptions: {
ts: {
skipTypeImports: true,
skipAsyncImports: true
}
}
});

console.log('-'.repeat(80));

const circular = madgeSetup.circular();

if (circular.length) {
console.error(circular.length, 'circular dependencies detected:\n');
for (let i = 0; i < circular.length; i++) {
printCircularDependency(circular[i], i + 1);
}
console.error('\nPlease fix the circular dependencies before proceeding.\n');

try {
const imagePath = join(__dirname, '../../madge');
mkdirSync(imagePath, { recursive: true });
const image = await madgeSetup.image(join(imagePath, 'circular.svg'), true);
console.log('Circular dependencies graph generated:', image);
} catch { console.warn('No image generated. Make sure Graphviz is in your $PATH if you want a visualization'); }

// TODO: Set this to 1 when we have fixed all circular dependencies
process.exit(0);
}

console.log('\nNo circular dependencies detected.\n');
process.exit(0);

/**
*
* @param {string[]} circular The circular dependencies.
* @param {number} idx The index of the circular dependency.
*/
function printCircularDependency(circular, idx) {
circular = circular.map(file => `${baseDir}/${file}`);
const circularPath = circular.join(' -> ');

if (IS_GITHUB_ACTIONS) {
console.error(`::error file=${circular[0]},title=Circular dependency::Circular dependencies detected: ${circularPath}`);
}
else if (IS_AZURE_PIPELINES) {
console.error(`##vso[task.logissue type=error;sourcepath=${circular[0]};]Circular dependencies detected: ${circularPath}`);
} else {
console.error(idx, '=', circularPath, '\n');
}

}
Loading