Chore: Add script to check for "illegal" imports (#18992)

* stop webhook module from importing itself

* wip script to list all module imports from a folder

* Update package.json

* fix ufm

* fix tip tap

* order by name

* fix stylesheet module

* fix self import

* fix self import

* pass folder as a variable

* implement folder variable

* allow to pass module name + prettify log output

* check for imports from core to packages and report error

* add comments

* run as part of test

* Update package.json

* add threshold

* Update index.js

* add report of module self imports

* Update index.js

* Update index.js

* Update src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* remove self imports in /extension-api

* fix order

* fix self imports

* fix self import

* fix self imports

* fix self imports

* fix collection self import

* fix self imports

* fix self import

* Update collection-pagination.element.ts

* Update entity-action.extension.ts

* fix self imports

* lower threshold

* fix self imports

* fix self imports

* fix self imports

* fix merge

* fix tree self imports

* Update sort-children-of-modal.element.ts

* Update section.context.ts

* change threshold

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Mads Rasmussen
2025-04-14 09:55:20 +02:00
committed by GitHub
parent 16ee7227ae
commit 684790df70
70 changed files with 285 additions and 135 deletions

View File

@@ -0,0 +1,189 @@
import fs from 'fs';
import path from 'path';
import { createImportMap } from '../importmap/index.js';
const ILLEGAL_CORE_IMPORTS_THRESHOLD = 10;
const SELF_IMPORTS_THRESHOLD = 13;
const clientProjectRoot = path.resolve(import.meta.dirname, '../../');
const modulePrefix = '@umbraco-cms/backoffice/';
// Regex patterns to match import and require statements
const importRegex = /import\s+(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g;
const importMap = createImportMap({
rootDir: './src',
});
const importMapEntries = Object.entries(importMap.imports);
const coreModules = importMapEntries.filter(([key, value]) => value.includes('/packages/core/'));
const packageModules = importMapEntries.filter(
([key, value]) => value.includes('/packages/') && !value.includes('/packages/core/'),
);
const packageModuleAliases = packageModules.map(([key]) => key);
/**
* Recursively walk through a directory and return all file paths
*/
function getAllFiles(dirPath, arrayOfFiles = []) {
const files = fs.readdirSync(dirPath);
files.forEach((file) => {
const fullPath = path.join(dirPath, file);
if (fs.statSync(fullPath).isDirectory()) {
getAllFiles(fullPath, arrayOfFiles);
} else {
arrayOfFiles.push(fullPath);
}
});
return arrayOfFiles;
}
/**
* Scan a file and extract import statements
*/
function getImportsInFile(filePath) {
const ext = path.extname(filePath);
if (filePath.includes('.stories.ts')) return [];
if (filePath.includes('.test.ts')) return [];
if (!['.ts'].includes(ext)) return [];
const content = fs.readFileSync(filePath, 'utf-8');
const imports = [];
let match;
while ((match = importRegex.exec(content)) !== null) {
imports.push({ type: 'import', value: match[1] });
}
return imports;
}
// Entry point
function getAllImportsFromFolder(startPath) {
if (!fs.existsSync(startPath)) {
console.error(`Path does not exist: ${startPath}`);
return;
}
const files = getAllFiles(startPath);
const imports = files
.map((file) => {
return getImportsInFile(file);
})
.flat()
.filter(Boolean);
return imports;
}
function getFolderPathFromModuleAlias(moduleAlias) {
const importMapEntry = importMapEntries.find(([key]) => key === moduleAlias);
if (!importMapEntry) {
throw new Error(`Module not found: ${moduleAlias}`);
}
// remove everything after the last /
const lastSlashIndex = importMapEntry[1].lastIndexOf('/');
const modulePath = importMapEntry[1].substring(0, lastSlashIndex);
return modulePath;
}
function getUmbracoModuleImportsInModule(moduleAlias) {
const modulePath = getFolderPathFromModuleAlias(moduleAlias);
const targetFolder = path.resolve(clientProjectRoot, modulePath);
const imports = getAllImportsFromFolder(targetFolder);
const importValues = imports.map((imp) => imp.value);
const uniqueImports = [...new Set(importValues)];
const umbracoModuleImports = uniqueImports
.filter((imp) => imp.startsWith(modulePrefix))
.sort((a, b) => a.localeCompare(b));
return umbracoModuleImports;
}
function reportIllegalImportsFromCore() {
console.error(`🔍 Scanning core modules for importing packages...`);
console.log(`\n`);
let total = 0;
// Check if any of the core modules import one of the package modules
// Run through all core modules and find the imports
coreModules.forEach(([alias, path]) => {
const importsInModule = getUmbracoModuleImportsInModule(alias);
// Check if any of the imports are in the package modules
const illegalImports = importsInModule.filter((imp) => packageModuleAliases.includes(imp));
// If there are no illegal imports, skip
if (illegalImports.length === 0) {
return;
}
// If there are illegal imports, log them
console.error(`🚨 ${alias}: Illegal imports found:`);
illegalImports.forEach((imp) => {
console.error(`${imp}`);
});
console.log(`\n`);
total++;
});
if (total > ILLEGAL_CORE_IMPORTS_THRESHOLD) {
throw new Error(
`Illegal imports found in ${total} core modules. ${total - ILLEGAL_CORE_IMPORTS_THRESHOLD} more than the threshold.`,
);
} else {
console.log(`✅ Success! Still under the threshold of ${ILLEGAL_CORE_IMPORTS_THRESHOLD} illegal imports. `);
}
console.log(`\n\n`);
}
function reportSelfImportsFromModules() {
console.error(`🔍 Scanning all modules for importing itself...`);
console.log(`\n`);
let total = 0;
importMapEntries.forEach(([alias, path]) => {
const importsInModule = getUmbracoModuleImportsInModule(alias);
const selfImports = importsInModule.filter((imp) => imp === alias);
// If there are no self imports, skip
if (selfImports.length === 0) {
return;
}
// If there are self imports, log them
console.error(`🚨 ${alias} is importing itself`);
total++;
});
console.log(`\n`);
if (total > SELF_IMPORTS_THRESHOLD) {
throw new Error(
`Self imports found in ${total} modules. ${total - SELF_IMPORTS_THRESHOLD} more than the threshold.`,
);
} else {
console.log(`✅ Success! Still under the threshold of ${SELF_IMPORTS_THRESHOLD} self imports.`);
}
console.log(`\n\n`);
}
function report() {
reportIllegalImportsFromCore();
reportSelfImportsFromModules();
}
report();
// TODO:
// - Check what packages another package depends on (not modules) - This will be used when we split the tsconfig into multiple configs
// - Check for circular module imports
// - Report if a module imports itself