chore: ARTEX 初始化
ARTEX — AI 自主渗透测试系统(Go 后端 + Next.js 前端,单二进制内嵌)。 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
commit
0ef8561411
287 files changed
+70178
No files matched your search
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
Reference in new issue
Block a user