fix: 收紧CSS所有权与历史兼容门禁

This commit is contained in:
hectorzhao
2026-09-05 09:33:34 +08:00
parent 64bfb9ad3d
commit ca1fc2847f
11 changed files with 670 additions and 41 deletions
+8 -35
View File
@@ -5,6 +5,8 @@ import process from 'node:process';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import postcss from 'postcss';
import { broadBusinessSelectors, verifyOwnership } from './css-policy.mjs';
export { broadBusinessSelectors } from './css-policy.mjs';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
@@ -24,18 +26,6 @@ export function analyzeCss(css) {
return result;
}
export function broadBusinessSelectors(css) {
const findings = [];
postcss.parse(css).walkRules((rule) => {
for (const selector of rule.selectors ?? [rule.selector]) {
if (/^(?:div|section|article|button|table|input|select|textarea)(?:\b|[ >+~:[.#])/.test(selector.trim())) {
findings.push(selector);
}
}
});
return findings;
}
export function verifyGlobalCssAbsent(repoRoot) {
const legacy = path.join(repoRoot, 'src/styles/global.css');
if (fs.existsSync(legacy)) throw new Error('src/styles/global.css 已完成迁移,禁止重新创建');
@@ -45,13 +35,6 @@ function read(relativePath) {
return fs.readFileSync(path.join(root, relativePath), 'utf8');
}
function currentCssFiles(directory) {
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const target = path.join(directory, entry.name);
return entry.isDirectory() ? currentCssFiles(target) : entry.name.endsWith('.css') ? [target] : [];
});
}
function changedCssFiles() {
if (!fs.existsSync(path.join(root, '.git'))) return { base: 'archive', files: [] };
const configuredBase = process.env.QUALITY_BASE_REF;
@@ -89,6 +72,12 @@ function baseCss(base, file) {
function verify() {
verifyGlobalCssAbsent(root);
const policy = JSON.parse(read('tools/quality/css-ownership.json'));
const { graph } = verifyOwnership(root, policy);
const mainStyles = graph.get('src/main.tsx').filter((file) => file.endsWith('.css'));
if (JSON.stringify(mainStyles) !== JSON.stringify(policy.mainImportOrder)) {
throw new Error('应用入口 CSS 层级与已评审基线不一致');
}
const baseline = JSON.parse(read('tools/quality/css-governance-baseline.json'));
const entry = read(baseline.entry);
const imports = [...entry.matchAll(/@import\s+['"](.+?)['"]/g)].map((match) =>
@@ -132,22 +121,6 @@ function verify() {
}
}
const sourceText = currentCssFiles(path.join(root, 'src'))
.map((file) => fs.readFileSync(file, 'utf8'))
.join('\n');
const codeText = fs
.readdirSync(path.join(root, 'src'), { recursive: true })
.filter((name) => /\.(?:ts|tsx)$/.test(String(name)))
.map((name) => fs.readFileSync(path.join(root, 'src', String(name)), 'utf8'))
.join('\n');
for (const file of currentCssFiles(path.join(root, 'src'))) {
const relative = path.relative(path.join(root, 'src'), file).replace(/\\/g, '/');
if (relative === 'styles/tokens.css' || relative === 'styles/reset.css') continue;
if (!sourceText.includes(path.basename(file)) && !codeText.includes(path.basename(file))) {
throw new Error(`${relative} 没有明确 import 所有者`);
}
}
const duplicateCount = selectorList.length - new Set(selectorList).size;
const digest = crypto.createHash('sha256').update(selectorList.join('\n')).digest('hex').slice(0, 12);
console.log(