119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
import { cp, readdir, mkdir, rm, unlink, writeFile } from "node:fs/promises";
|
|
import { watch } from "node:fs";
|
|
import { join, resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { renderTheme, type Theme } from "src/core";
|
|
import { getCollectedCss } from "src/types/vars";
|
|
import "styles";
|
|
|
|
const root = resolve(import.meta.dir, "..");
|
|
const outputDir = join(root, ".gitea-data", "gitea", "public", "assets", "css");
|
|
const templatesDir = join(root, "templates");
|
|
const templatesOutputDir = join(root, ".gitea-data", "gitea", "templates");
|
|
const watchDirs = [join(root, "src"), join(root, "styles"), join(root, "themes"), templatesDir];
|
|
|
|
const themeEntries = [
|
|
{
|
|
name: "modern-light",
|
|
source: join(root, "themes", "modern-light.css.ts"),
|
|
output: "theme-banana-modern-light.css",
|
|
displayName: "banana Modern Light",
|
|
},
|
|
{
|
|
name: "modern-dark",
|
|
source: join(root, "themes", "modern-dark.css.ts"),
|
|
output: "theme-banana-modern-dark.css",
|
|
displayName: "banana Modern Dark",
|
|
},
|
|
] as const;
|
|
|
|
const autoTheme = {
|
|
output: "theme-banana-modern-auto.css",
|
|
displayName: "banana Modern Auto",
|
|
lightOutput: "theme-banana-modern-light.css",
|
|
darkOutput: "theme-banana-modern-dark.css",
|
|
} as const;
|
|
|
|
const expectedOutputs = new Set<string>([...themeEntries.map(theme => theme.output), autoTheme.output]);
|
|
const args = new Set(Bun.argv.slice(2));
|
|
const isWatch = args.has("--watch");
|
|
const isMinify = args.has("--minify");
|
|
|
|
function compileStyles(): string {
|
|
const css = getCollectedCss().replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
if (!isMinify) return css;
|
|
return css.replace(/\s+/g, " ").replace(/\s*([{}:;,>+~])\s*/g, "$1").trim();
|
|
}
|
|
|
|
async function cleanOldThemes(): Promise<void> {
|
|
await mkdir(outputDir, { recursive: true });
|
|
const existingFiles = await readdir(outputDir, { withFileTypes: true });
|
|
|
|
for (const file of existingFiles) {
|
|
if (file.isFile() && file.name.startsWith("theme-") && file.name.endsWith(".css") && !expectedOutputs.has(file.name)) {
|
|
await unlink(join(outputDir, file.name));
|
|
}
|
|
}
|
|
}
|
|
|
|
async function copyTemplates(): Promise<void> {
|
|
await rm(templatesOutputDir, { recursive: true, force: true });
|
|
await cp(templatesDir, templatesOutputDir, { recursive: true });
|
|
console.log(`copied ${templatesOutputDir}`);
|
|
}
|
|
|
|
function meta(displayName: string): string {
|
|
return `gitea-theme-meta-info{--theme-display-name:"${displayName}";}`;
|
|
}
|
|
|
|
async function loadTheme(source: string): Promise<Theme> {
|
|
const module = await import(`${pathToFileURL(source).href}?t=${Date.now()}`);
|
|
return module.default as Theme;
|
|
}
|
|
|
|
async function buildTheme() {
|
|
await cleanOldThemes();
|
|
await copyTemplates();
|
|
const styles = compileStyles();
|
|
const buildVersion = Date.now();
|
|
|
|
for (const theme of themeEntries) {
|
|
const themeCss = renderTheme(await loadTheme(theme.source));
|
|
const outputFile = join(outputDir, theme.output);
|
|
await writeFile(outputFile, `${meta(theme.displayName)}${themeCss}\n${styles}`, "utf8");
|
|
console.log(`built ${outputFile}`);
|
|
}
|
|
|
|
const autoCss = [
|
|
`@import "./${autoTheme.lightOutput}?v=${buildVersion}" (prefers-color-scheme: light);`,
|
|
`@import "./${autoTheme.darkOutput}?v=${buildVersion}" (prefers-color-scheme: dark);`,
|
|
meta(autoTheme.displayName),
|
|
].join("\n");
|
|
const autoOutputFile = join(outputDir, autoTheme.output);
|
|
await writeFile(autoOutputFile, autoCss, "utf8");
|
|
console.log(`built ${autoOutputFile}`);
|
|
}
|
|
|
|
let queuedBuild: Timer | undefined;
|
|
|
|
function queueBuild() {
|
|
if (queuedBuild) {
|
|
clearTimeout(queuedBuild);
|
|
}
|
|
queuedBuild = setTimeout(() => {
|
|
buildTheme().catch(error => {
|
|
console.error(error);
|
|
});
|
|
}, 80);
|
|
}
|
|
|
|
await buildTheme();
|
|
|
|
if (isWatch) {
|
|
console.log(`watching ${watchDirs.join(", ")}`);
|
|
for (const watchDir of watchDirs) {
|
|
watch(watchDir, { recursive: true }, queueBuild);
|
|
}
|
|
await new Promise(() => {});
|
|
}
|