chore: ARTEX 初始化

ARTEX — AI 自主渗透测试系统(Go 后端 + Next.js 前端,单二进制内嵌)。

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Autumn-27andClaude Opus 4.8 committed 2026-07-26 04:27:45 -04:00
commit 0ef8561411
287 files changed
+70178

No files matched your search

+131
View File
@@ -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);
}
+113
View File
@@ -0,0 +1,113 @@
/**
* Boot script that reads user preference values (theme mode, theme preset,
* content layout, navbar style) from cookies or localStorage based on the
* configured persistence mode.
*
* Runs early in <head> to apply the correct data attributes before hydration,
* preventing layout or theme flicker and keeping RootLayout fully static.
*/
import { PREFERENCE_DEFAULTS, PREFERENCE_PERSISTENCE } from "@/lib/preferences/preferences-config";
export function ThemeBootScript() {
const persistence = JSON.stringify({
theme_mode: PREFERENCE_PERSISTENCE.theme_mode,
theme_preset: PREFERENCE_PERSISTENCE.theme_preset,
font: PREFERENCE_PERSISTENCE.font,
content_layout: PREFERENCE_PERSISTENCE.content_layout,
navbar_style: PREFERENCE_PERSISTENCE.navbar_style,
sidebar_variant: PREFERENCE_PERSISTENCE.sidebar_variant,
sidebar_collapsible: PREFERENCE_PERSISTENCE.sidebar_collapsible,
});
const defaults = JSON.stringify({
theme_mode: PREFERENCE_DEFAULTS.theme_mode,
theme_preset: PREFERENCE_DEFAULTS.theme_preset,
font: PREFERENCE_DEFAULTS.font,
content_layout: PREFERENCE_DEFAULTS.content_layout,
navbar_style: PREFERENCE_DEFAULTS.navbar_style,
sidebar_variant: PREFERENCE_DEFAULTS.sidebar_variant,
sidebar_collapsible: PREFERENCE_DEFAULTS.sidebar_collapsible,
});
const code = `
(function () {
try {
var root = document.documentElement;
var PERSISTENCE = ${persistence};
var DEFAULTS = ${defaults};
function readCookie(name) {
var match = document.cookie.split("; ").find(function(c) {
return c.startsWith(name + "=");
});
return match ? decodeURIComponent(match.split("=")[1]) : null;
}
function readLocal(name) {
try {
return window.localStorage.getItem(name);
} catch (e) {
return null;
}
}
function readPreference(key, fallback) {
var mode = PERSISTENCE[key];
var value = null;
if (mode === "localStorage") {
value = readLocal(key);
}
if (!value && (mode === "client-cookie" || mode === "server-cookie")) {
value = readCookie(key);
}
if (!value || typeof value !== "string") {
return fallback;
}
return value;
}
var rawMode = readPreference("theme_mode", DEFAULTS.theme_mode);
var rawPreset = readPreference("theme_preset", DEFAULTS.theme_preset);
var rawFont = readPreference("font", DEFAULTS.font);
var rawContentLayout = readPreference("content_layout", DEFAULTS.content_layout);
var rawNavbarStyle = readPreference("navbar_style", DEFAULTS.navbar_style);
var rawSidebarVariant = readPreference("sidebar_variant", DEFAULTS.sidebar_variant);
var rawSidebarCollapsible = readPreference("sidebar_collapsible", DEFAULTS.sidebar_collapsible);
var isValidMode = rawMode === "dark" || rawMode === "light" || rawMode === "system";
var mode = isValidMode ? rawMode : DEFAULTS.theme_mode;
var resolvedMode =
mode === "system" && window.matchMedia
? (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
: mode;
var preset = rawPreset || DEFAULTS.theme_preset;
var font = rawFont || DEFAULTS.font;
var contentLayout = rawContentLayout || DEFAULTS.content_layout;
var navbarStyle = rawNavbarStyle || DEFAULTS.navbar_style;
var sidebarVariant = rawSidebarVariant || DEFAULTS.sidebar_variant;
var sidebarCollapsible = rawSidebarCollapsible || DEFAULTS.sidebar_collapsible;
root.classList.toggle("dark", resolvedMode === "dark");
root.setAttribute("data-theme-mode", mode);
root.setAttribute("data-theme-preset", preset);
root.setAttribute("data-font", font);
root.setAttribute("data-content-layout", contentLayout);
root.setAttribute("data-navbar-style", navbarStyle);
root.setAttribute("data-sidebar-variant", sidebarVariant);
root.setAttribute("data-sidebar-collapsible", sidebarCollapsible);
root.style.colorScheme = resolvedMode === "dark" ? "dark" : "light";
} catch (e) {
console.warn("ThemeBootScript error:", e);
}
})();
`;
/* biome-ignore lint/security/noDangerouslySetInnerHtml: required for pre-hydration boot script */
return <script dangerouslySetInnerHTML={{ __html: code }} />;
}