Files
ARTEX/web/src/scripts/generate-theme-presets.ts
T
Autumn-27andClaude Opus 4.8 0ef8561411 chore: ARTEX 初始化
ARTEX — AI 自主渗透测试系统(Go 后端 + Next.js 前端,单二进制内嵌)。

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-26 04:27:45 -04:00

132 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Script: generate-theme-presets.ts
*
* This script scans the /styles/presets directory for CSS files containing theme definitions.
* It extracts `label:`, `value:`, and primary color definitions (`--primary`) for both light and dark modes.
* These primary colors are used to visually represent each theme in the UI (e.g., colored dots or theme previews).
* Default theme colors are fetched from /app/globals.css.
* All extracted metadata is injected into a marked section of the /lib/preferences/theme.ts file.
*
* Usage:
* - During local development, run manually after adding any new theme preset:
* npm run generate:presets
* - Ensure that each new CSS preset includes `label:` and `value:` comments.
* - This generation step is currently automated using a Husky pre-push hook.
* - You may optionally integrate it directly into a build step if preferred.
*/
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
const presetDir = path.resolve(__dirname, "../styles/presets");
if (!fs.existsSync(presetDir)) {
console.error(`❌ Preset directory not found at: ${presetDir}`);
process.exit(1);
}
const outputPath = path.resolve(__dirname, "../lib/preferences/theme.ts");
const files = fs.readdirSync(presetDir).filter((file) => file.endsWith(".css"));
if (files.length === 0) {
console.warn("⚠️ No preset CSS files found. Only default preset will be included.");
}
const presets = files.map((file) => {
const filePath = path.join(presetDir, file);
const content = fs.readFileSync(filePath, "utf8");
const labelMatch = content.match(/label:\s*(.+)/);
const valueMatch = content.match(/value:\s*(.+)/);
if (!labelMatch) {
console.warn(`⚠️ No 'label:' found in ${file}, using filename as fallback.`);
}
if (!valueMatch) {
console.warn(`⚠️ No 'value:' found in ${file}, using filename as fallback.`);
}
const label = labelMatch?.[1]?.trim() ?? file.replace(".css", "");
const value = valueMatch?.[1]?.trim() ?? file.replace(".css", "");
const lightPrimaryMatch = content.match(/:root\[data-theme-preset="[^"]*"\][\s\S]*?--primary:\s*([^;]+);/);
const darkPrimaryMatch = content.match(/\.dark:root\[data-theme-preset="[^"]*"\][\s\S]*?--primary:\s*([^;]+);/);
const primary = {
light: lightPrimaryMatch?.[1]?.trim() ?? "",
dark: darkPrimaryMatch?.[1]?.trim() ?? "",
};
if (!lightPrimaryMatch || !darkPrimaryMatch) {
console.warn(`⚠️ Missing --primary for ${file} (light or dark). Check CSS syntax.`);
}
return { label, value, primary };
});
const globalStylesPath = path.resolve(__dirname, "../app/globals.css");
let globalContent = "";
try {
globalContent = fs.readFileSync(globalStylesPath, "utf8");
} catch (err) {
console.error(`❌ Could not read globals.css at ${globalStylesPath}`);
console.error(err);
process.exit(1);
}
const defaultLightPrimaryRegex = /:root\s*{[^}]*--primary:\s*([^;]+);/;
const defaultDarkPrimaryRegex = /\.dark\s*{[^}]*--primary:\s*([^;]+);/;
const defaultLightPrimaryMatch = defaultLightPrimaryRegex.exec(globalContent);
const defaultDarkPrimaryMatch = defaultDarkPrimaryRegex.exec(globalContent);
const defaultPrimary = {
light: defaultLightPrimaryMatch?.[1]?.trim() ?? "",
dark: defaultDarkPrimaryMatch?.[1]?.trim() ?? "",
};
presets.unshift({ label: "Default", value: "default", primary: defaultPrimary });
const generatedBlock = `// --- generated:themePresets:start ---
export const THEME_PRESET_OPTIONS = ${JSON.stringify(presets, null, 2)} as const;
export const THEME_PRESET_VALUES = THEME_PRESET_OPTIONS.map((p) => p.value);
export type ThemePreset = (typeof THEME_PRESET_OPTIONS)[number]["value"];
// --- generated:themePresets:end ---`;
const fileContent = fs.readFileSync(outputPath, "utf8");
const updated = fileContent.replace(
/\/\/ --- generated:themePresets:start ---[\s\S]*?\/\/ --- generated:themePresets:end ---/,
generatedBlock,
);
function main() {
const biomeBin = require.resolve("@biomejs/biome/bin/biome");
const formatted = execFileSync(process.execPath, [biomeBin, "format", "--stdin-file-path", outputPath], {
input: updated,
encoding: "utf8",
});
if (formatted === fileContent) {
console.log("ℹ️ No changes in theme.ts");
return;
}
fs.writeFileSync(outputPath, formatted);
console.log("✅ theme.ts updated with new theme presets");
}
try {
main();
} catch (err) {
console.error("❌ Unexpected error while generating theme presets:", err);
process.exit(1);
}