120 lines
3.5 KiB
JavaScript
120 lines
3.5 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import postcss from "postcss";
|
|
|
|
const unsupportedColor = /(?:color-mix|oklch|oklab|lab|lch)\(/i;
|
|
const assetsDir = path.resolve("dist/assets");
|
|
|
|
if (!fs.existsSync(assetsDir)) {
|
|
throw new Error("dist/assets 不存在,请先运行 pnpm build");
|
|
}
|
|
|
|
const cssFiles = fs
|
|
.readdirSync(assetsDir)
|
|
.filter((file) => file.endsWith(".css"));
|
|
|
|
if (cssFiles.length === 0) {
|
|
throw new Error("dist/assets 中没有 CSS 构建产物");
|
|
}
|
|
|
|
const failures = [];
|
|
let modernDeclarations = 0;
|
|
|
|
const requiredLegacyFallbacks = [
|
|
{
|
|
file: "src/components/ui/textarea.tsx",
|
|
pattern: /rows=\{rows\s*\?\?\s*3\}/,
|
|
description: "Textarea needs a native rows fallback",
|
|
},
|
|
{
|
|
file: "src/components/ai-elements/prompt-input.tsx",
|
|
pattern: /<InputGroup className="h-auto min-h-16">/,
|
|
description: "Prompt textarea must not rely on :has() for its parent height",
|
|
},
|
|
{
|
|
file: "src/core/utils/mermaid.ts",
|
|
pattern: /htmlLabels:\s*false[\s\S]*secure:\s*\[[\s\S]*"htmlLabels"/,
|
|
description: "Sandbox flowcharts must use native SVG labels in legacy browsers",
|
|
},
|
|
{
|
|
file: "src/core/utils/legacy-html-preview.ts",
|
|
pattern: /export function downlevelModernColors[\s\S]*function injectLegacyChartShim/,
|
|
description: "Generated HTML charts must downlevel modern CSS colors for Chrome 109",
|
|
},
|
|
];
|
|
|
|
for (const file of cssFiles) {
|
|
const root = postcss.parse(
|
|
fs.readFileSync(path.join(assetsDir, file), "utf8"),
|
|
{ from: file },
|
|
);
|
|
|
|
root.walkDecls((declaration) => {
|
|
if (!unsupportedColor.test(declaration.value)) return;
|
|
modernDeclarations += 1;
|
|
|
|
let ancestor = declaration.parent;
|
|
while (ancestor) {
|
|
if (
|
|
ancestor.type === "atrule" &&
|
|
ancestor.name === "supports" &&
|
|
unsupportedColor.test(ancestor.params)
|
|
) {
|
|
return;
|
|
}
|
|
ancestor = ancestor.parent;
|
|
}
|
|
|
|
let previous = declaration.prev();
|
|
while (previous?.type === "comment") previous = previous.prev();
|
|
const hasSafeFallback =
|
|
!declaration.prop.startsWith("--") &&
|
|
previous?.type === "decl" &&
|
|
previous.prop === declaration.prop &&
|
|
!unsupportedColor.test(previous.value);
|
|
|
|
if (!hasSafeFallback) {
|
|
failures.push(
|
|
`${file}:${declaration.source?.start?.line ?? "?"} ${declaration.prop}: ${declaration.value}`,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
function walk(directory) {
|
|
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
const fullPath = path.join(directory, entry.name);
|
|
return entry.isDirectory() ? walk(fullPath) : [fullPath];
|
|
});
|
|
}
|
|
|
|
for (const file of walk(path.resolve("src"))) {
|
|
if (!/\.[jt]sx?$/.test(file)) continue;
|
|
const source = fs.readFileSync(file, "utf8");
|
|
if (unsupportedColor.test(source)) {
|
|
failures.push(
|
|
`${path.relative(process.cwd(), file)} contains a runtime-only modern color function`,
|
|
);
|
|
}
|
|
}
|
|
|
|
for (const fallback of requiredLegacyFallbacks) {
|
|
const source = fs.readFileSync(path.resolve(fallback.file), "utf8");
|
|
if (!fallback.pattern.test(source)) {
|
|
failures.push(`${fallback.file}: ${fallback.description}`);
|
|
}
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error("Legacy Chrome CSS compatibility check failed:\n");
|
|
console.error(failures.slice(0, 50).join("\n"));
|
|
if (failures.length > 50) {
|
|
console.error(`... and ${failures.length - 50} more`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(
|
|
`Legacy Chrome (100+) CSS compatibility check passed (${modernDeclarations} guarded modern declarations).`,
|
|
);
|