mirror of
https://github.com/rustfs/console.git
synced 2026-08-29 03:52:28 +08:00
189 lines
5.6 KiB
JavaScript
189 lines
5.6 KiB
JavaScript
import fs from "node:fs"
|
|
import path from "node:path"
|
|
import { fileURLToPath } from "node:url"
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = path.dirname(__filename)
|
|
const root = path.resolve(__dirname, "..")
|
|
const runtimeRoot = path.join(root, ".theme-runtime")
|
|
const backupRoot = path.join(runtimeRoot, "backup")
|
|
const stateFile = path.join(runtimeRoot, "state.json")
|
|
const themesRoot = path.join(root, "themes")
|
|
|
|
const themeIdRaw = process.env.NEXT_PUBLIC_THEME_ID?.trim() || process.env.THEME_ID?.trim() || "default"
|
|
const themeIdPattern = /^[A-Za-z0-9_-]+$/
|
|
if (!themeIdPattern.test(themeIdRaw)) {
|
|
throw new Error(`[theme] Invalid theme id "${themeIdRaw}". Only [A-Za-z0-9_-] are allowed.`)
|
|
}
|
|
|
|
const themeId = themeIdRaw
|
|
const themeRoot = path.resolve(themesRoot, themeId)
|
|
if (!themeRoot.startsWith(`${themesRoot}${path.sep}`) && themeRoot !== themesRoot) {
|
|
throw new Error(`[theme] Invalid theme path: ${themeRoot}`)
|
|
}
|
|
|
|
if (!fs.existsSync(themeRoot)) {
|
|
throw new Error(`[theme] Theme "${themeId}" not found: ${themeRoot}`)
|
|
}
|
|
|
|
const mappings = [
|
|
{ from: "components", to: "components" },
|
|
{ from: "assets", to: "assets" },
|
|
{ from: "locales", to: path.join("public", "themes", themeId, "locales") },
|
|
{ from: "public", to: "public" },
|
|
{ from: "config", to: "config" },
|
|
]
|
|
|
|
function rmDirRecursive(dir) {
|
|
if (!fs.existsSync(dir)) return
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
for (const entry of entries) {
|
|
const current = path.join(dir, entry.name)
|
|
if (entry.isDirectory()) {
|
|
rmDirRecursive(current)
|
|
} else {
|
|
fs.unlinkSync(current)
|
|
}
|
|
}
|
|
fs.rmdirSync(dir)
|
|
}
|
|
|
|
function ensureParentDir(filePath) {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
}
|
|
|
|
function readState() {
|
|
if (!fs.existsSync(stateFile)) return []
|
|
try {
|
|
const data = JSON.parse(fs.readFileSync(stateFile, "utf8"))
|
|
return Array.isArray(data) ? data : []
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
function writeState(entries) {
|
|
fs.mkdirSync(runtimeRoot, { recursive: true })
|
|
fs.writeFileSync(stateFile, `${JSON.stringify(entries, null, 2)}\n`, "utf8")
|
|
}
|
|
|
|
function restorePreviousOverrides() {
|
|
const entries = readState()
|
|
for (const entry of entries) {
|
|
if (!entry || typeof entry.path !== "string") continue
|
|
|
|
const targetPath = path.join(root, entry.path)
|
|
const backupPath = path.join(backupRoot, entry.path)
|
|
|
|
if (entry.existed) {
|
|
if (fs.existsSync(backupPath)) {
|
|
ensureParentDir(targetPath)
|
|
fs.copyFileSync(backupPath, targetPath)
|
|
}
|
|
} else if (fs.existsSync(targetPath)) {
|
|
fs.unlinkSync(targetPath)
|
|
}
|
|
}
|
|
|
|
if (fs.existsSync(backupRoot)) {
|
|
rmDirRecursive(backupRoot)
|
|
}
|
|
if (fs.existsSync(stateFile)) {
|
|
fs.unlinkSync(stateFile)
|
|
}
|
|
}
|
|
|
|
const backedUp = new Map()
|
|
const stateEntries = []
|
|
|
|
function backupBeforeWrite(targetPath) {
|
|
const relativePath = path.relative(root, targetPath)
|
|
if (backedUp.has(relativePath)) return
|
|
|
|
const existed = fs.existsSync(targetPath)
|
|
stateEntries.push({ path: relativePath, existed })
|
|
backedUp.set(relativePath, true)
|
|
writeState(stateEntries)
|
|
|
|
if (!existed) return
|
|
|
|
const backupPath = path.join(backupRoot, relativePath)
|
|
ensureParentDir(backupPath)
|
|
fs.copyFileSync(targetPath, backupPath)
|
|
}
|
|
|
|
function copyDirRecursive(src, dest) {
|
|
if (!fs.existsSync(src)) return
|
|
|
|
const entries = fs.readdirSync(src, { withFileTypes: true })
|
|
fs.mkdirSync(dest, { recursive: true })
|
|
|
|
for (const entry of entries) {
|
|
if (entry.name === ".gitkeep") continue
|
|
|
|
const from = path.join(src, entry.name)
|
|
const to = path.join(dest, entry.name)
|
|
if (entry.isDirectory()) {
|
|
copyDirRecursive(from, to)
|
|
} else if (entry.isFile()) {
|
|
fs.mkdirSync(path.dirname(to), { recursive: true })
|
|
backupBeforeWrite(to)
|
|
fs.copyFileSync(from, to)
|
|
}
|
|
}
|
|
}
|
|
|
|
function listLocaleFiles(src, base = src) {
|
|
if (!fs.existsSync(src)) return []
|
|
|
|
const files = []
|
|
const entries = fs.readdirSync(src, { withFileTypes: true })
|
|
for (const entry of entries) {
|
|
if (entry.name === ".gitkeep") continue
|
|
const full = path.join(src, entry.name)
|
|
if (entry.isDirectory()) {
|
|
files.push(...listLocaleFiles(full, base))
|
|
continue
|
|
}
|
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue
|
|
files.push(path.relative(base, full))
|
|
}
|
|
|
|
return files
|
|
}
|
|
|
|
restorePreviousOverrides()
|
|
writeState([])
|
|
|
|
for (const mapping of mappings) {
|
|
copyDirRecursive(path.join(themeRoot, mapping.from), path.join(root, mapping.to))
|
|
}
|
|
|
|
const themeLogoPath = path.join(root, "assets", "logo.svg")
|
|
const themeDarkLogoPath = path.join(root, "assets", "logo-dark.svg")
|
|
if (fs.existsSync(themeLogoPath) && !fs.existsSync(themeDarkLogoPath)) {
|
|
backupBeforeWrite(themeDarkLogoPath)
|
|
ensureParentDir(themeDarkLogoPath)
|
|
fs.copyFileSync(themeLogoPath, themeDarkLogoPath)
|
|
}
|
|
|
|
const themeLocaleRoot = path.join(themeRoot, "locales")
|
|
const localeFiles = listLocaleFiles(themeLocaleRoot)
|
|
const localeIndex = localeFiles.map((file) => file.replace(/\.json$/, ""))
|
|
const localeIndexTargetPath = path.join(root, "public", "themes", themeId, "locales", "index.json")
|
|
backupBeforeWrite(localeIndexTargetPath)
|
|
ensureParentDir(localeIndexTargetPath)
|
|
fs.writeFileSync(localeIndexTargetPath, `${JSON.stringify(localeIndex, null, 2)}\n`, "utf8")
|
|
|
|
const themeManifestPath = path.join(themeRoot, "manifest.json")
|
|
const activeManifestPath = path.join(root, "themes", ".active", "manifest.json")
|
|
if (!fs.existsSync(themeManifestPath)) {
|
|
throw new Error(`[theme] Missing manifest.json in theme: ${themeRoot}`)
|
|
}
|
|
ensureParentDir(activeManifestPath)
|
|
fs.copyFileSync(themeManifestPath, activeManifestPath)
|
|
|
|
writeState(stateEntries)
|
|
|
|
console.log(`[theme] Applied theme "${themeId}" overrides.`)
|