Poked at the 62 "no parseable source" skips from the first pass and found two things the pipeline didn't handle: several community repos genuinely ship colors.toml too — some in Omarchy 4's own semantic schema (best case, no derivation needed at all, same quality as the official 22), others reusing the filename for an unrelated flat color0..color15 scheme — and a handful of alacritty.toml files use 0xrrggbb instead of #rrggbb. palette.ts's loadPalette is now a thin wrapper over two exported pieces (parseTomlText, paletteFromRecord) so the community pipeline can attempt the real schema first without duplicating the reader; falls through to the color16 or ANSI-derived paths otherwise. generate-themes.ts --check still reports 0 changes for the official set — the refactor is behavior-preserving there, the lowercase-hex normalization it picked up along the way touched 7 official theme files cosmetically (their source colors.toml has uppercase hex), regenerated for real rather than left inconsistent with their own source. 147 -> 191 community themes ported. Of the original 62 unparseable repos, most turned out to be simply gone (404 on GitHub — repos deleted or renamed since the site listed them); the rest needed one of the two format additions above. 20 themes remain stuck: 16 dead repos, 2 with no repo link at all, and the same handful of genuinely low-contrast source palettes (mostly Solarized variants) where fixing contrast would mean changing the author's actual colours rather than porting them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
|
|
// Reading an Omarchy theme's `colors.toml`.
|
|
//
|
|
// Omarchy 4 ships a genuinely semantic palette rather than sixteen ANSI slots: three background depths,
|
|
// four foreground weights, an accent, a selection and a muted, plus the named hues. That is why this port
|
|
// can be a mapping at all — the previous one had to guess, and the guessing is what produced themes with
|
|
// no surfaces and unreadable highlights.
|
|
//
|
|
// Verified across all 22 themes in basecamp/omarchy@master: every one declares the 24 keys below.
|
|
|
|
export type Palette = {
|
|
mode: 'dark' | 'light';
|
|
accent: string;
|
|
selection: string;
|
|
muted: string;
|
|
background: string;
|
|
darkBackground: string;
|
|
darkerBackground: string;
|
|
lighterBackground: string;
|
|
foreground: string;
|
|
darkForeground: string;
|
|
lightForeground: string;
|
|
brightForeground: string;
|
|
red: string;
|
|
yellow: string;
|
|
green: string;
|
|
cyan: string;
|
|
blue: string;
|
|
magenta: string;
|
|
/** Not in every theme — 19 of 22. Falls back to yellow-red mid, which is what orange is. */
|
|
orange: string;
|
|
brightRed: string;
|
|
brightYellow: string;
|
|
brightGreen: string;
|
|
brightCyan: string;
|
|
brightBlue: string;
|
|
brightMagenta: string;
|
|
};
|
|
|
|
/**
|
|
* A deliberately small TOML reader: this file is flat `key = "value"` and nothing else.
|
|
*
|
|
* Exported (not just used by `loadPalette` below) because a community theme can ship this exact shape
|
|
* too — see `port-community-themes.ts`, which reads it from a fetched string rather than a file path.
|
|
*/
|
|
export function parseTomlText(text: string): Record<string, string> {
|
|
const out: Record<string, string> = {};
|
|
for (const line of text.split('\n')) {
|
|
const m = line.match(/^\s*([a-z_0-9]+)\s*=\s*"([^"]*)"/i);
|
|
// basecamp/omarchy's own files are already lowercase hex; a community `colors.toml` isn't guaranteed
|
|
// to be, so normalize here rather than leaving the two sources visibly inconsistent.
|
|
if (m) out[m[1]!] = m[2]!.toLowerCase();
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** The 24-key contract, given an already-parsed record rather than a path — see `parseTomlText`. */
|
|
export function paletteFromRecord(t: Record<string, string>, label: string): Palette {
|
|
const need = (key: string): string => {
|
|
const v = t[key];
|
|
if (!v) throw new Error(`${label}: missing "${key}"`);
|
|
return v;
|
|
};
|
|
|
|
return {
|
|
mode: t.mode === 'light' ? 'light' : 'dark',
|
|
accent: need('accent'),
|
|
selection: need('selection'),
|
|
muted: need('muted'),
|
|
background: need('background'),
|
|
darkBackground: need('dark_background'),
|
|
darkerBackground: need('darker_background'),
|
|
lighterBackground: need('lighter_background'),
|
|
foreground: need('foreground'),
|
|
darkForeground: need('dark_foreground'),
|
|
lightForeground: need('light_foreground'),
|
|
brightForeground: need('bright_foreground'),
|
|
red: need('red'),
|
|
yellow: need('yellow'),
|
|
green: need('green'),
|
|
cyan: need('cyan'),
|
|
blue: need('blue'),
|
|
magenta: need('magenta'),
|
|
orange: t.orange ?? need('yellow'),
|
|
brightRed: need('bright_red'),
|
|
brightYellow: need('bright_yellow'),
|
|
brightGreen: need('bright_green'),
|
|
brightCyan: need('bright_cyan'),
|
|
brightBlue: need('bright_blue'),
|
|
brightMagenta: need('bright_magenta'),
|
|
};
|
|
}
|
|
|
|
export function loadPalette(tomlPath: string): Palette {
|
|
return paletteFromRecord(parseTomlText(readFileSync(tomlPath, 'utf8')), tomlPath);
|
|
}
|