Something went wrong. Try again.
fork of https://github.com/xzlee3/nvim-github-theme
Something went wrong. Try again.
15 kB · 445 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446#!/usr/bin/env node
import { execFileSync } from "node:child_process";import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync,} from "node:fs";import { tmpdir } from "node:os";import { dirname, join, resolve } from "node:path";import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));const ROOT = resolve(__dirname, "..");const SOURCES_PATH = join(__dirname, "sources.json");const OUTPUT_PATH = join(ROOT, "lua", "github_theme", "generated", "themes.lua");
const VARIANTS = [ { slug: "github_light_default", file: "light-default.json", theme: "light", isDark: false, }, { slug: "github_light_high_contrast", file: "light-high-contrast.json", theme: "light_high_contrast", isDark: false, }, { slug: "github_light_colorblind", file: "light-colorblind.json", theme: "light_colorblind", isDark: false, }, { slug: "github_dark_default", file: "dark-default.json", theme: "dark", isDark: true, }, { slug: "github_dark_high_contrast", file: "dark-high-contrast.json", theme: "dark_high_contrast", isDark: true, }, { slug: "github_dark_colorblind", file: "dark-colorblind.json", theme: "dark_colorblind", isDark: true, }, { slug: "github_dark_dimmed", file: "dark-dimmed.json", theme: "dark_dimmed", isDark: true, },];
const LUA_BACKSLASH = "\\";const LUA_ESCAPED_BACKSLASH = String.raw`\\`;const LUA_ESCAPED_SINGLE_QUOTE = String.raw`\'`;
function parseArgs(argv) { const args = { check: false, keepTemp: false, };
for (let index = 0; index < argv.length; index += 1) { const value = argv[index];
if (value === "--check") { args.check = true; continue; }
if (value === "--keep-temp") { args.keepTemp = true; continue; }
if (value === "--github-vscode-theme") { index += 1; args.githubVscodeTheme = argv[index]; continue; }
if (value === "--primer") { index += 1; args.primerPrimitives = argv[index]; continue; }
throw new Error(`Unknown argument: ${value}`); }
return args;}
function loadPinnedSources() { return JSON.parse(readFileSync(SOURCES_PATH, "utf8"));}
function run(command, args, options = {}) { return execFileSync(command, args, { cwd: options.cwd ?? ROOT, encoding: "utf8", stdio: options.stdio ?? "pipe", });}
function packPackage(tempRoot, spec) { const packed = run("npm", ["pack", "--silent", spec], { cwd: tempRoot }).trim(); return join(tempRoot, packed.split("\n").at(-1));}
function extractPackage(tarballPath, destination) { mkdirSync(destination, { recursive: true }); run("tar", ["-xzf", tarballPath, "-C", destination]); return join(destination, "package");}
function ensureLink(target, linkPath) { const parent = dirname(linkPath); mkdirSync(parent, { recursive: true });
if (existsSync(linkPath)) { rmSync(linkPath, { recursive: true, force: true }); }
symlinkSync(target, linkPath, "dir");}
function parseHex(value) { if (typeof value !== "string" || value.length === 0) { return null; }
return value.toUpperCase();}
function scopeList(token) { if (Array.isArray(token.scope)) { return token.scope; }
if (typeof token.scope === "string") { return [token.scope]; }
return [];}
function findTokenSettings(theme, scopes) { for (const token of theme.tokenColors ?? []) { const tokenScopes = scopeList(token); if (scopes.some((scope) => tokenScopes.includes(scope))) { return token.settings ?? {}; } }
return {};}
function normalizeTheme(variant, theme) { const colors = theme.colors ?? {};
const comment = findTokenSettings(theme, ["comment"]); const constant = findTokenSettings(theme, ["constant"]); const character = findTokenSettings(theme, ["constant.character"]); const string = findTokenSettings(theme, ["string"]); const regexp = findTokenSettings(theme, ["source.regexp", "string.regexp"]); const func = findTokenSettings(theme, ["entity.name.function"]); const keyword = findTokenSettings(theme, ["keyword"]); const type = findTokenSettings(theme, ["entity.name"]); const variable = findTokenSettings(theme, ["variable"]); const variableOther = findTokenSettings(theme, ["variable.other"]); const property = findTokenSettings(theme, ["meta.property-name"]); const support = findTokenSettings(theme, ["support"]); const tag = findTokenSettings(theme, ["entity.name.tag"]); const link = findTokenSettings(theme, ["constant.other.reference.link", "string.other.link"]); const markupHeading = findTokenSettings(theme, ["markup.heading"]); const markupQuote = findTokenSettings(theme, ["markup.quote"]); const markupRaw = findTokenSettings(theme, ["markup.inline.raw"]); const punctuationSpecial = findTokenSettings(theme, ["punctuation.section.embedded"]); const diffDeleted = findTokenSettings(theme, ["markup.deleted"]); const diffInserted = findTokenSettings(theme, ["markup.inserted"]); const diffChanged = findTokenSettings(theme, ["markup.changed"]);
return { name: theme.name, is_dark: variant.isDark, upstream_theme: variant.theme, palette: { bg: parseHex(colors["editor.background"]), fg: parseHex(colors["editor.foreground"]), fg_muted: parseHex(colors.descriptionForeground), fg_subtle: parseHex(colors["editorLineNumber.foreground"]), bg_float: parseHex(colors["editorWidget.background"]), bg_popup: parseHex(colors["quickInput.background"] ?? colors["dropdown.background"]), bg_sidebar: parseHex(colors["sideBar.background"]), bg_statusline: parseHex(colors["statusBar.background"]), bg_visual: parseHex( colors["editor.selectionBackground"] ?? colors["editor.inactiveSelectionBackground"] ?? colors["editor.linkedEditingBackground"] ), selection_fg: parseHex(colors["editor.selectionForeground"]), bg_search: parseHex(colors["editor.findMatchBackground"]), bg_incsearch: parseHex( colors["editor.findMatchHighlightBackground"] ?? colors["editor.wordHighlightBackground"] ), bg_cursorline: parseHex(colors["editor.lineHighlightBackground"]), bg_menu: parseHex(colors["dropdown.background"]), bg_menu_sel: parseHex(colors["list.focusBackground"]), border: parseHex(colors["panel.border"] ?? colors["editorGroup.border"]), border_float: parseHex(colors["dropdown.border"]), accent: parseHex(colors["textLink.foreground"]), cursor: parseHex(colors["editorCursor.foreground"]), line_nr: parseHex(colors["editorLineNumber.foreground"]), line_nr_active: parseHex(colors["editorLineNumber.activeForeground"]), whitespace: parseHex(colors["editorWhitespace.foreground"]), match_paren_bg: parseHex(colors["editorBracketMatch.background"]), error: parseHex(colors.errorForeground ?? colors["editorError.foreground"]), warning: parseHex(colors["editorWarning.foreground"] ?? colors["notificationsWarningIcon.foreground"]), info: parseHex(colors["notificationsInfoIcon.foreground"] ?? colors["textLink.foreground"]), hint: parseHex(colors["editorInlayHint.foreground"] ?? colors.descriptionForeground), git_add: parseHex(colors["gitDecoration.addedResourceForeground"]), git_change: parseHex(colors["gitDecoration.modifiedResourceForeground"]), git_delete: parseHex(colors["gitDecoration.deletedResourceForeground"]), diff_add: parseHex(diffInserted.foreground ?? colors["gitDecoration.addedResourceForeground"]), diff_add_bg: parseHex( diffInserted.background ?? colors["diffEditor.insertedTextBackground"] ?? colors["diffEditor.insertedLineBackground"] ), diff_delete: parseHex(diffDeleted.foreground ?? colors["gitDecoration.deletedResourceForeground"]), diff_delete_bg: parseHex( diffDeleted.background ?? colors["diffEditor.removedTextBackground"] ?? colors["diffEditor.removedLineBackground"] ), diff_change: parseHex(diffChanged.foreground ?? colors["gitDecoration.modifiedResourceForeground"]), diff_change_bg: parseHex(diffChanged.background), comment: parseHex(comment.foreground), constant: parseHex(constant.foreground), character: parseHex(character.foreground ?? string.foreground), string: parseHex(string.foreground), regexp: parseHex(regexp.foreground ?? string.foreground), func: parseHex(func.foreground), keyword: parseHex(keyword.foreground), type: parseHex(type.foreground), variable: parseHex(variable.foreground), variable_other: parseHex(variableOther.foreground ?? colors["editor.foreground"]), property: parseHex(property.foreground), support: parseHex(support.foreground), tag: parseHex(tag.foreground), link: parseHex(link.foreground ?? colors["textLink.foreground"]), heading: parseHex(markupHeading.foreground ?? colors["textLink.foreground"]), quote: parseHex(markupQuote.foreground ?? comment.foreground), inline_raw: parseHex(markupRaw.foreground ?? string.foreground), punctuation_special: parseHex(punctuationSpecial.foreground ?? keyword.foreground), ansi_black: parseHex(colors["terminal.ansiBlack"]), ansi_red: parseHex(colors["terminal.ansiRed"]), ansi_green: parseHex(colors["terminal.ansiGreen"]), ansi_yellow: parseHex(colors["terminal.ansiYellow"]), ansi_blue: parseHex(colors["terminal.ansiBlue"]), ansi_magenta: parseHex(colors["terminal.ansiMagenta"]), ansi_cyan: parseHex(colors["terminal.ansiCyan"]), ansi_white: parseHex(colors["terminal.ansiWhite"]), ansi_bright_black: parseHex(colors["terminal.ansiBrightBlack"]), ansi_bright_red: parseHex(colors["terminal.ansiBrightRed"]), ansi_bright_green: parseHex(colors["terminal.ansiBrightGreen"]), ansi_bright_yellow: parseHex(colors["terminal.ansiBrightYellow"]), ansi_bright_blue: parseHex(colors["terminal.ansiBrightBlue"]), ansi_bright_magenta: parseHex(colors["terminal.ansiBrightMagenta"]), ansi_bright_cyan: parseHex(colors["terminal.ansiBrightCyan"]), ansi_bright_white: parseHex(colors["terminal.ansiBrightWhite"]), }, };}
function generateModernThemes(themeDir, tempRoot) { const generatorPath = join(tempRoot, "generate-modern-themes.cjs"); const themesDir = join(themeDir, "themes"); const runner = `const fs = require("fs");const path = require("path");const getTheme = require(${JSON.stringify(join(themeDir, "src", "theme.js"))});
const variants = ${JSON.stringify( VARIANTS.map((variant) => ({ file: variant.file, theme: variant.theme, })) )};
const names = { light: "GitHub Light Default", light_high_contrast: "GitHub Light High Contrast", light_colorblind: "GitHub Light Colorblind", dark: "GitHub Dark Default", dark_high_contrast: "GitHub Dark High Contrast", dark_colorblind: "GitHub Dark Colorblind", dark_dimmed: "GitHub Dark Dimmed",};
fs.mkdirSync(${JSON.stringify(themesDir)}, { recursive: true });
for (const variant of variants) { const theme = getTheme({ theme: variant.theme, name: names[variant.theme], });
fs.writeFileSync( path.join(${JSON.stringify(themesDir)}, variant.file), JSON.stringify(theme, null, 2) );}`;
writeFileSync(generatorPath, runner, "utf8"); run("node", [generatorPath], { cwd: themeDir });}
function luaString(value) { return `'${String(value) .replaceAll(LUA_BACKSLASH, LUA_ESCAPED_BACKSLASH) .replaceAll("'", LUA_ESCAPED_SINGLE_QUOTE)}'`;}
function toLua(value, indent = 0) { const pad = " ".repeat(indent); const nextPad = " ".repeat(indent + 1);
if (value === null || value === undefined) { return "nil"; }
if (typeof value === "string") { return luaString(value); }
if (typeof value === "boolean" || typeof value === "number") { return String(value); }
if (Array.isArray(value)) { if (value.length === 0) { return "{}"; }
const items = value.map((entry) => `${nextPad}${toLua(entry, indent + 1)}`); return `{\n${items.join(",\n")}\n${pad}}`; }
const entries = Object.entries(value); if (entries.length === 0) { return "{}"; }
const rendered = entries.map(([key, entry]) => `${nextPad}${key} = ${toLua(entry, indent + 1)}`); return `{\n${rendered.join(",\n")}\n${pad}}`;}
function renderLua(meta, variants) { const payload = { meta, variants, };
return `return ${toLua(payload)}\n`;}
function main() { const pinned = loadPinnedSources(); const args = parseArgs(process.argv.slice(2)); const githubVscodeTheme = args.githubVscodeTheme ?? pinned.githubVscodeTheme; const primerPrimitives = args.primerPrimitives ?? pinned.primerPrimitives;
const tempRoot = mkdtempSync(join(tmpdir(), "github-theme-sync-"));
try { const themeTarball = packPackage(tempRoot, `github-vscode-theme@${githubVscodeTheme}`); const primerTarball = packPackage(tempRoot, `@primer/primitives@${primerPrimitives}`);
const themeDir = extractPackage(themeTarball, join(tempRoot, "theme")); const primerDir = extractPackage(primerTarball, join(tempRoot, "primer")); const themePackage = JSON.parse(readFileSync(join(themeDir, "package.json"), "utf8")); const chromaSpec = pinned.chromaJs; if (!chromaSpec) { throw new Error("Missing chromaJs pin in scripts/sources.json"); } if (!themePackage.devDependencies["chroma-js"]) { throw new Error("github-vscode-theme no longer declares chroma-js in devDependencies"); } const chromaTarball = packPackage(tempRoot, `chroma-js@${chromaSpec}`); const chromaDir = extractPackage(chromaTarball, join(tempRoot, "chroma"));
ensureLink(primerDir, join(themeDir, "node_modules", "@primer", "primitives")); ensureLink(chromaDir, join(themeDir, "node_modules", "chroma-js"));
generateModernThemes(themeDir, tempRoot);
const normalizedVariants = {};
for (const variant of VARIANTS) { const theme = JSON.parse(readFileSync(join(themeDir, "themes", variant.file), "utf8")); normalizedVariants[variant.slug] = normalizeTheme(variant, theme); }
const rendered = renderLua( { github_vscode_theme: githubVscodeTheme, primer_primitives: primerPrimitives, }, normalizedVariants );
if (args.check) { const current = readFileSync(OUTPUT_PATH, "utf8"); if (current !== rendered) { throw new Error("Generated theme data is out of date. Run: node scripts/sync_intent.mjs"); } process.stdout.write("Generated theme data is in sync.\n"); return; }
writeFileSync(OUTPUT_PATH, rendered, "utf8"); process.stdout.write(`Updated ${OUTPUT_PATH}\n`); } finally { if (args.keepTemp) { process.stdout.write(`Kept temp workspace: ${tempRoot}\n`); } else { rmSync(tempRoot, { recursive: true, force: true }); } }}
main();