formalconf 2.0.10 → 2.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/formalconf.js +388 -63
- package/package.json +1 -1
package/dist/formalconf.js
CHANGED
|
@@ -467,7 +467,7 @@ function StatusIndicator({
|
|
|
467
467
|
// package.json
|
|
468
468
|
var package_default = {
|
|
469
469
|
name: "formalconf",
|
|
470
|
-
version: "2.0.
|
|
470
|
+
version: "2.0.12",
|
|
471
471
|
description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
|
|
472
472
|
type: "module",
|
|
473
473
|
main: "./dist/formalconf.js",
|
|
@@ -4666,13 +4666,13 @@ function useThemeGrid({
|
|
|
4666
4666
|
import { parseArgs as parseArgs4 } from "util";
|
|
4667
4667
|
import {
|
|
4668
4668
|
readdirSync as readdirSync9,
|
|
4669
|
-
existsSync as
|
|
4669
|
+
existsSync as existsSync13,
|
|
4670
4670
|
rmSync,
|
|
4671
4671
|
symlinkSync,
|
|
4672
4672
|
unlinkSync as unlinkSync2,
|
|
4673
4673
|
copyFileSync
|
|
4674
4674
|
} from "fs";
|
|
4675
|
-
import { join as
|
|
4675
|
+
import { join as join12, basename as basename4 } from "path";
|
|
4676
4676
|
|
|
4677
4677
|
// src/lib/theme-parser.ts
|
|
4678
4678
|
init_runtime();
|
|
@@ -4784,9 +4784,318 @@ async function runHooks(hookType, env = {}) {
|
|
|
4784
4784
|
};
|
|
4785
4785
|
}
|
|
4786
4786
|
|
|
4787
|
+
// src/lib/gtk/colloid.ts
|
|
4788
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4789
|
+
import { join as join6 } from "path";
|
|
4790
|
+
init_runtime();
|
|
4791
|
+
|
|
4792
|
+
// src/lib/gtk/palette.ts
|
|
4793
|
+
function hexToRgb(hex) {
|
|
4794
|
+
const clean = hex.replace("#", "");
|
|
4795
|
+
return {
|
|
4796
|
+
r: parseInt(clean.substring(0, 2), 16),
|
|
4797
|
+
g: parseInt(clean.substring(2, 4), 16),
|
|
4798
|
+
b: parseInt(clean.substring(4, 6), 16)
|
|
4799
|
+
};
|
|
4800
|
+
}
|
|
4801
|
+
function rgbToHex(r, g, b) {
|
|
4802
|
+
const toHex = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
|
|
4803
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
4804
|
+
}
|
|
4805
|
+
function interpolateColor(color1, color2, factor) {
|
|
4806
|
+
const c1 = hexToRgb(color1);
|
|
4807
|
+
const c2 = hexToRgb(color2);
|
|
4808
|
+
return rgbToHex(c1.r + (c2.r - c1.r) * factor, c1.g + (c2.g - c1.g) * factor, c1.b + (c2.b - c1.b) * factor);
|
|
4809
|
+
}
|
|
4810
|
+
function interpolateGreyscale(bg, fg) {
|
|
4811
|
+
const steps = 19;
|
|
4812
|
+
const colors5 = [];
|
|
4813
|
+
for (let i = 0;i < steps; i++) {
|
|
4814
|
+
const factor = i / (steps - 1);
|
|
4815
|
+
colors5.push(interpolateColor(bg, fg, factor));
|
|
4816
|
+
}
|
|
4817
|
+
return colors5;
|
|
4818
|
+
}
|
|
4819
|
+
function lighten(hex, percent) {
|
|
4820
|
+
const { r, g, b } = hexToRgb(hex);
|
|
4821
|
+
const factor = percent / 100;
|
|
4822
|
+
return rgbToHex(r + (255 - r) * factor, g + (255 - g) * factor, b + (255 - b) * factor);
|
|
4823
|
+
}
|
|
4824
|
+
function darken(hex, percent) {
|
|
4825
|
+
const { r, g, b } = hexToRgb(hex);
|
|
4826
|
+
const factor = 1 - percent / 100;
|
|
4827
|
+
return rgbToHex(r * factor, g * factor, b * factor);
|
|
4828
|
+
}
|
|
4829
|
+
function createColloidPalette(palette, mode) {
|
|
4830
|
+
const isDark = mode === "dark";
|
|
4831
|
+
const grey = isDark ? interpolateGreyscale(palette.background, palette.foreground) : interpolateGreyscale(palette.foreground, palette.background);
|
|
4832
|
+
const orderedGrey = isDark ? grey : [...grey].reverse();
|
|
4833
|
+
const accentBase = palette.accent || palette.color4;
|
|
4834
|
+
return {
|
|
4835
|
+
grey: orderedGrey,
|
|
4836
|
+
black: palette.color0,
|
|
4837
|
+
white: palette.color7,
|
|
4838
|
+
red: {
|
|
4839
|
+
dark: darken(palette.color1, 10),
|
|
4840
|
+
light: lighten(palette.color1, 10)
|
|
4841
|
+
},
|
|
4842
|
+
green: {
|
|
4843
|
+
dark: darken(palette.color2, 10),
|
|
4844
|
+
light: lighten(palette.color2, 10)
|
|
4845
|
+
},
|
|
4846
|
+
yellow: {
|
|
4847
|
+
dark: darken(palette.color3, 10),
|
|
4848
|
+
light: lighten(palette.color3, 10)
|
|
4849
|
+
},
|
|
4850
|
+
blue: {
|
|
4851
|
+
dark: darken(palette.color4, 10),
|
|
4852
|
+
light: lighten(palette.color4, 10)
|
|
4853
|
+
},
|
|
4854
|
+
purple: {
|
|
4855
|
+
dark: darken(palette.color5, 10),
|
|
4856
|
+
light: lighten(palette.color5, 10)
|
|
4857
|
+
},
|
|
4858
|
+
teal: {
|
|
4859
|
+
dark: darken(palette.color6, 10),
|
|
4860
|
+
light: lighten(palette.color6, 10)
|
|
4861
|
+
},
|
|
4862
|
+
accent: {
|
|
4863
|
+
dark: darken(accentBase, 10),
|
|
4864
|
+
light: lighten(accentBase, 10)
|
|
4865
|
+
}
|
|
4866
|
+
};
|
|
4867
|
+
}
|
|
4868
|
+
function generateColloidScss(palette, mode) {
|
|
4869
|
+
const colloid = createColloidPalette(palette, mode);
|
|
4870
|
+
const greyNames = [
|
|
4871
|
+
"050",
|
|
4872
|
+
"100",
|
|
4873
|
+
"150",
|
|
4874
|
+
"200",
|
|
4875
|
+
"250",
|
|
4876
|
+
"300",
|
|
4877
|
+
"350",
|
|
4878
|
+
"400",
|
|
4879
|
+
"450",
|
|
4880
|
+
"500",
|
|
4881
|
+
"550",
|
|
4882
|
+
"600",
|
|
4883
|
+
"650",
|
|
4884
|
+
"700",
|
|
4885
|
+
"750",
|
|
4886
|
+
"800",
|
|
4887
|
+
"850",
|
|
4888
|
+
"900",
|
|
4889
|
+
"950"
|
|
4890
|
+
];
|
|
4891
|
+
let scss = `// FormalConf Generated Color Palette
|
|
4892
|
+
// Auto-generated - do not edit manually
|
|
4893
|
+
|
|
4894
|
+
// Greyscale (interpolated from theme background/foreground)
|
|
4895
|
+
`;
|
|
4896
|
+
for (let i = 0;i < greyNames.length; i++) {
|
|
4897
|
+
scss += `$grey-${greyNames[i]}: ${colloid.grey[i]};
|
|
4898
|
+
`;
|
|
4899
|
+
}
|
|
4900
|
+
scss += `
|
|
4901
|
+
// Base colors
|
|
4902
|
+
$black: ${colloid.black};
|
|
4903
|
+
$white: ${colloid.white};
|
|
4904
|
+
|
|
4905
|
+
// Semantic colors - dark variants
|
|
4906
|
+
$red-dark: ${colloid.red.dark};
|
|
4907
|
+
$green-dark: ${colloid.green.dark};
|
|
4908
|
+
$yellow-dark: ${colloid.yellow.dark};
|
|
4909
|
+
$blue-dark: ${colloid.blue.dark};
|
|
4910
|
+
$purple-dark: ${colloid.purple.dark};
|
|
4911
|
+
$teal-dark: ${colloid.teal.dark};
|
|
4912
|
+
|
|
4913
|
+
// Semantic colors - light variants
|
|
4914
|
+
$red-light: ${colloid.red.light};
|
|
4915
|
+
$green-light: ${colloid.green.light};
|
|
4916
|
+
$yellow-light: ${colloid.yellow.light};
|
|
4917
|
+
$blue-light: ${colloid.blue.light};
|
|
4918
|
+
$purple-light: ${colloid.purple.light};
|
|
4919
|
+
$teal-light: ${colloid.teal.light};
|
|
4920
|
+
|
|
4921
|
+
// Accent colors (primary buttons, links, highlights)
|
|
4922
|
+
$default-dark: ${colloid.accent.dark};
|
|
4923
|
+
$default-light: ${colloid.accent.light};
|
|
4924
|
+
$links: ${palette.accent || palette.color4};
|
|
4925
|
+
|
|
4926
|
+
// Backgrounds
|
|
4927
|
+
$background: ${palette.background};
|
|
4928
|
+
$foreground: ${palette.foreground};
|
|
4929
|
+
`;
|
|
4930
|
+
return scss;
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
// src/lib/gtk/colloid.ts
|
|
4934
|
+
var COLLOID_REPO_URL = "https://github.com/vinceliuice/Colloid-gtk-theme.git";
|
|
4935
|
+
async function checkGtkDependencies() {
|
|
4936
|
+
const [git, sassc] = await Promise.all([
|
|
4937
|
+
commandExists("git"),
|
|
4938
|
+
commandExists("sassc")
|
|
4939
|
+
]);
|
|
4940
|
+
const missing = [];
|
|
4941
|
+
if (!git)
|
|
4942
|
+
missing.push("git");
|
|
4943
|
+
if (!sassc)
|
|
4944
|
+
missing.push("sassc");
|
|
4945
|
+
return { git, sassc, missing };
|
|
4946
|
+
}
|
|
4947
|
+
async function getDependencyInstructions(missing) {
|
|
4948
|
+
if (missing.length === 0)
|
|
4949
|
+
return "";
|
|
4950
|
+
const distro = await getLinuxDistro();
|
|
4951
|
+
const instructions = [];
|
|
4952
|
+
for (const dep of missing) {
|
|
4953
|
+
let instruction = `Missing: ${dep}
|
|
4954
|
+
`;
|
|
4955
|
+
switch (distro) {
|
|
4956
|
+
case "arch":
|
|
4957
|
+
instruction += ` Install: sudo pacman -S ${dep}`;
|
|
4958
|
+
break;
|
|
4959
|
+
case "debian":
|
|
4960
|
+
case "ubuntu":
|
|
4961
|
+
instruction += ` Install: sudo apt install ${dep}`;
|
|
4962
|
+
break;
|
|
4963
|
+
case "fedora":
|
|
4964
|
+
instruction += ` Install: sudo dnf install ${dep}`;
|
|
4965
|
+
break;
|
|
4966
|
+
case "opensuse":
|
|
4967
|
+
instruction += ` Install: sudo zypper install ${dep}`;
|
|
4968
|
+
break;
|
|
4969
|
+
default:
|
|
4970
|
+
instruction += ` Install via your package manager`;
|
|
4971
|
+
}
|
|
4972
|
+
instructions.push(instruction);
|
|
4973
|
+
}
|
|
4974
|
+
return instructions.join(`
|
|
4975
|
+
`);
|
|
4976
|
+
}
|
|
4977
|
+
async function cloneColloidRepo() {
|
|
4978
|
+
await ensureDir2(GTK_DIR);
|
|
4979
|
+
const result = await exec([
|
|
4980
|
+
"git",
|
|
4981
|
+
"clone",
|
|
4982
|
+
"--depth",
|
|
4983
|
+
"1",
|
|
4984
|
+
COLLOID_REPO_URL,
|
|
4985
|
+
COLLOID_DIR
|
|
4986
|
+
]);
|
|
4987
|
+
return result.success;
|
|
4988
|
+
}
|
|
4989
|
+
async function updateColloidRepo() {
|
|
4990
|
+
const result = await exec(["git", "pull", "--rebase"], COLLOID_DIR);
|
|
4991
|
+
return result.success;
|
|
4992
|
+
}
|
|
4993
|
+
async function ensureColloidRepo() {
|
|
4994
|
+
if (existsSync7(join6(COLLOID_DIR, ".git"))) {
|
|
4995
|
+
return updateColloidRepo();
|
|
4996
|
+
}
|
|
4997
|
+
return cloneColloidRepo();
|
|
4998
|
+
}
|
|
4999
|
+
async function writeCustomPalette(palette, mode) {
|
|
5000
|
+
const scss = generateColloidScss(palette, mode);
|
|
5001
|
+
const palettePath = join6(COLLOID_DIR, "src", "sass", "_color-palette-formalconf.scss");
|
|
5002
|
+
await writeFile(palettePath, scss);
|
|
5003
|
+
}
|
|
5004
|
+
async function patchTweaksFile(themeName) {
|
|
5005
|
+
const tweaksContent = `// FormalConf theme: ${themeName}
|
|
5006
|
+
// Auto-patched to use custom color palette
|
|
5007
|
+
|
|
5008
|
+
@import 'color-palette-formalconf';
|
|
5009
|
+
|
|
5010
|
+
$tweaks: true;
|
|
5011
|
+
$colorscheme: true;
|
|
5012
|
+
`;
|
|
5013
|
+
const tweaksPath = join6(COLLOID_DIR, "src", "sass", "_tweaks-temp.scss");
|
|
5014
|
+
await writeFile(tweaksPath, tweaksContent);
|
|
5015
|
+
}
|
|
5016
|
+
async function runColloidInstall(options) {
|
|
5017
|
+
const args = [
|
|
5018
|
+
"./install.sh",
|
|
5019
|
+
"-n",
|
|
5020
|
+
`formalconf-${options.themeName}`,
|
|
5021
|
+
"-c",
|
|
5022
|
+
options.mode === "dark" ? "dark" : "light"
|
|
5023
|
+
];
|
|
5024
|
+
if (options.installLibadwaita !== false) {
|
|
5025
|
+
args.push("-l");
|
|
5026
|
+
}
|
|
5027
|
+
const tweaks = options.tweaks && options.tweaks.length > 0 ? options.tweaks : ["normal"];
|
|
5028
|
+
args.push("--tweaks", ...tweaks);
|
|
5029
|
+
const result = await exec(args, COLLOID_DIR);
|
|
5030
|
+
return result.exitCode;
|
|
5031
|
+
}
|
|
5032
|
+
function getGtkThemeName(themeName, mode) {
|
|
5033
|
+
const modeCapitalized = mode === "dark" ? "Dark" : "Light";
|
|
5034
|
+
return `Colloid-formalconf-${themeName}-${modeCapitalized}`;
|
|
5035
|
+
}
|
|
5036
|
+
async function applyGtkTheme(theme, mode) {
|
|
5037
|
+
if (getOS() !== "linux") {
|
|
5038
|
+
return {
|
|
5039
|
+
success: true,
|
|
5040
|
+
themeName: "",
|
|
5041
|
+
skipped: true,
|
|
5042
|
+
skipReason: "GTK theming only available on Linux"
|
|
5043
|
+
};
|
|
5044
|
+
}
|
|
5045
|
+
const palette = mode === "dark" ? theme.dark : theme.light;
|
|
5046
|
+
if (!palette) {
|
|
5047
|
+
return {
|
|
5048
|
+
success: false,
|
|
5049
|
+
themeName: "",
|
|
5050
|
+
error: `Theme does not have a ${mode} palette`
|
|
5051
|
+
};
|
|
5052
|
+
}
|
|
5053
|
+
const themeName = theme.title.toLowerCase().replace(/\s+/g, "-");
|
|
5054
|
+
const deps = await checkGtkDependencies();
|
|
5055
|
+
if (deps.missing.length > 0) {
|
|
5056
|
+
const instructions = await getDependencyInstructions(deps.missing);
|
|
5057
|
+
return {
|
|
5058
|
+
success: false,
|
|
5059
|
+
themeName: "",
|
|
5060
|
+
error: `Missing dependencies:
|
|
5061
|
+
${instructions}`
|
|
5062
|
+
};
|
|
5063
|
+
}
|
|
5064
|
+
const repoReady = await ensureColloidRepo();
|
|
5065
|
+
if (!repoReady) {
|
|
5066
|
+
return {
|
|
5067
|
+
success: false,
|
|
5068
|
+
themeName: "",
|
|
5069
|
+
error: "Failed to clone/update Colloid repository"
|
|
5070
|
+
};
|
|
5071
|
+
}
|
|
5072
|
+
await writeCustomPalette(palette, mode);
|
|
5073
|
+
await patchTweaksFile(themeName);
|
|
5074
|
+
const gtkConfig = theme.gtk || {};
|
|
5075
|
+
const options = {
|
|
5076
|
+
themeName,
|
|
5077
|
+
mode,
|
|
5078
|
+
variant: gtkConfig.variant,
|
|
5079
|
+
tweaks: gtkConfig.tweaks,
|
|
5080
|
+
installLibadwaita: true
|
|
5081
|
+
};
|
|
5082
|
+
const exitCode = await runColloidInstall(options);
|
|
5083
|
+
if (exitCode !== 0) {
|
|
5084
|
+
return {
|
|
5085
|
+
success: false,
|
|
5086
|
+
themeName: "",
|
|
5087
|
+
error: `Colloid install.sh failed with exit code ${exitCode}`
|
|
5088
|
+
};
|
|
5089
|
+
}
|
|
5090
|
+
const installedThemeName = getGtkThemeName(themeName, mode);
|
|
5091
|
+
return {
|
|
5092
|
+
success: true,
|
|
5093
|
+
themeName: installedThemeName
|
|
5094
|
+
};
|
|
5095
|
+
}
|
|
4787
5096
|
// src/lib/theme-config.ts
|
|
4788
5097
|
import { hostname } from "os";
|
|
4789
|
-
import { existsSync as
|
|
5098
|
+
import { existsSync as existsSync8, readFileSync, writeFileSync } from "fs";
|
|
4790
5099
|
var DEFAULT_CONFIG = {
|
|
4791
5100
|
version: 1,
|
|
4792
5101
|
defaultTheme: null,
|
|
@@ -4796,7 +5105,7 @@ function getDeviceHostname() {
|
|
|
4796
5105
|
return hostname();
|
|
4797
5106
|
}
|
|
4798
5107
|
function loadThemeConfig() {
|
|
4799
|
-
if (!
|
|
5108
|
+
if (!existsSync8(THEME_CONFIG_PATH)) {
|
|
4800
5109
|
return { ...DEFAULT_CONFIG, devices: {} };
|
|
4801
5110
|
}
|
|
4802
5111
|
try {
|
|
@@ -4862,8 +5171,8 @@ function getDefaultTheme() {
|
|
|
4862
5171
|
|
|
4863
5172
|
// src/lib/theme-v2/loader.ts
|
|
4864
5173
|
init_runtime();
|
|
4865
|
-
import { existsSync as
|
|
4866
|
-
import { join as
|
|
5174
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5 } from "fs";
|
|
5175
|
+
import { join as join7, basename as basename2 } from "path";
|
|
4867
5176
|
|
|
4868
5177
|
// src/lib/theme-v2/color.ts
|
|
4869
5178
|
function isValidHex(hex) {
|
|
@@ -4881,7 +5190,7 @@ function normalizeHex(hex) {
|
|
|
4881
5190
|
}
|
|
4882
5191
|
return hex.toUpperCase();
|
|
4883
5192
|
}
|
|
4884
|
-
function
|
|
5193
|
+
function hexToRgb2(hex) {
|
|
4885
5194
|
const normalized = normalizeHex(hex);
|
|
4886
5195
|
if (!isValidHex(normalized)) {
|
|
4887
5196
|
throw new Error(`Invalid hex color: ${hex}`);
|
|
@@ -4893,7 +5202,7 @@ function hexToRgb(hex) {
|
|
|
4893
5202
|
}
|
|
4894
5203
|
function hexToColorVariable(hex) {
|
|
4895
5204
|
const normalized = normalizeHex(hex);
|
|
4896
|
-
const { r, g, b } =
|
|
5205
|
+
const { r, g, b } = hexToRgb2(normalized);
|
|
4897
5206
|
return {
|
|
4898
5207
|
hex: normalized,
|
|
4899
5208
|
strip: normalized.slice(1),
|
|
@@ -5142,7 +5451,7 @@ ${validationErrors}`);
|
|
|
5142
5451
|
}
|
|
5143
5452
|
}
|
|
5144
5453
|
async function loadThemeJson(themePath) {
|
|
5145
|
-
if (!
|
|
5454
|
+
if (!existsSync9(themePath)) {
|
|
5146
5455
|
throw new ThemeLoadError(themePath, "file not found");
|
|
5147
5456
|
}
|
|
5148
5457
|
let content;
|
|
@@ -5172,14 +5481,14 @@ function getAvailableModes(theme) {
|
|
|
5172
5481
|
return modes;
|
|
5173
5482
|
}
|
|
5174
5483
|
async function listJsonThemes() {
|
|
5175
|
-
if (!
|
|
5484
|
+
if (!existsSync9(THEMES_DIR)) {
|
|
5176
5485
|
return [];
|
|
5177
5486
|
}
|
|
5178
5487
|
const entries = readdirSync5(THEMES_DIR, { withFileTypes: true });
|
|
5179
5488
|
const themes = [];
|
|
5180
5489
|
for (const entry of entries) {
|
|
5181
5490
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
5182
|
-
const path =
|
|
5491
|
+
const path = join7(THEMES_DIR, entry.name);
|
|
5183
5492
|
try {
|
|
5184
5493
|
const theme = await loadThemeJson(path);
|
|
5185
5494
|
themes.push({
|
|
@@ -5198,7 +5507,7 @@ async function listJsonThemes() {
|
|
|
5198
5507
|
|
|
5199
5508
|
// src/lib/template-engine/engine.ts
|
|
5200
5509
|
init_runtime();
|
|
5201
|
-
import { join as
|
|
5510
|
+
import { join as join9 } from "path";
|
|
5202
5511
|
|
|
5203
5512
|
// src/lib/template-engine/modifiers.ts
|
|
5204
5513
|
var VALID_MODIFIERS = [
|
|
@@ -5331,14 +5640,14 @@ function renderDualModeTemplate(template, contexts) {
|
|
|
5331
5640
|
|
|
5332
5641
|
// src/lib/template-engine/versioning.ts
|
|
5333
5642
|
init_runtime();
|
|
5334
|
-
import { existsSync as
|
|
5335
|
-
import { join as
|
|
5643
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
|
|
5644
|
+
import { join as join8 } from "path";
|
|
5336
5645
|
var DEFAULT_MANIFEST = {
|
|
5337
5646
|
version: 1,
|
|
5338
5647
|
templates: {}
|
|
5339
5648
|
};
|
|
5340
5649
|
async function loadTemplatesManifest() {
|
|
5341
|
-
if (!
|
|
5650
|
+
if (!existsSync10(TEMPLATES_MANIFEST_PATH)) {
|
|
5342
5651
|
return { ...DEFAULT_MANIFEST };
|
|
5343
5652
|
}
|
|
5344
5653
|
try {
|
|
@@ -5353,7 +5662,7 @@ async function saveTemplatesManifest(manifest) {
|
|
|
5353
5662
|
await writeFile(TEMPLATES_MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
5354
5663
|
}
|
|
5355
5664
|
async function loadBundledManifest() {
|
|
5356
|
-
if (!
|
|
5665
|
+
if (!existsSync10(BUNDLED_MANIFEST_PATH)) {
|
|
5357
5666
|
return { version: 1, templates: {} };
|
|
5358
5667
|
}
|
|
5359
5668
|
try {
|
|
@@ -5404,13 +5713,13 @@ async function installTemplate(templateName) {
|
|
|
5404
5713
|
if (!bundledMeta) {
|
|
5405
5714
|
throw new Error(`Template '${templateName}' not found in bundled templates`);
|
|
5406
5715
|
}
|
|
5407
|
-
const sourcePath =
|
|
5408
|
-
if (!
|
|
5716
|
+
const sourcePath = join8(BUNDLED_TEMPLATES_DIR, templateName);
|
|
5717
|
+
if (!existsSync10(sourcePath)) {
|
|
5409
5718
|
throw new Error(`Template file not found: ${sourcePath}`);
|
|
5410
5719
|
}
|
|
5411
5720
|
await ensureDir2(TEMPLATES_DIR);
|
|
5412
5721
|
const content = await readText(sourcePath);
|
|
5413
|
-
const destPath =
|
|
5722
|
+
const destPath = join8(TEMPLATES_DIR, templateName);
|
|
5414
5723
|
await writeFile(destPath, content);
|
|
5415
5724
|
const manifest = await loadTemplatesManifest();
|
|
5416
5725
|
manifest.templates[templateName] = {
|
|
@@ -5472,7 +5781,7 @@ function getOutputFilename(templateName) {
|
|
|
5472
5781
|
return output;
|
|
5473
5782
|
}
|
|
5474
5783
|
async function listInstalledTemplates() {
|
|
5475
|
-
if (!
|
|
5784
|
+
if (!existsSync10(TEMPLATES_DIR)) {
|
|
5476
5785
|
return [];
|
|
5477
5786
|
}
|
|
5478
5787
|
const entries = readdirSync6(TEMPLATES_DIR, { withFileTypes: true });
|
|
@@ -5481,7 +5790,7 @@ async function listInstalledTemplates() {
|
|
|
5481
5790
|
if (entry.isFile() && entry.name.endsWith(".template")) {
|
|
5482
5791
|
templates.push({
|
|
5483
5792
|
name: entry.name,
|
|
5484
|
-
path:
|
|
5793
|
+
path: join8(TEMPLATES_DIR, entry.name),
|
|
5485
5794
|
outputName: getOutputFilename(entry.name),
|
|
5486
5795
|
type: getTemplateType(entry.name),
|
|
5487
5796
|
partialMode: getPartialMode(entry.name)
|
|
@@ -5655,7 +5964,7 @@ async function renderTemplateFile(templateFile, theme, mode) {
|
|
|
5655
5964
|
return {
|
|
5656
5965
|
template: templateFile,
|
|
5657
5966
|
content,
|
|
5658
|
-
outputPath:
|
|
5967
|
+
outputPath: join9(GENERATED_DIR, templateFile.outputName)
|
|
5659
5968
|
};
|
|
5660
5969
|
}
|
|
5661
5970
|
async function renderAllTemplates(theme, mode) {
|
|
@@ -5689,7 +5998,7 @@ async function generateNeovimConfigFile(theme, mode) {
|
|
|
5689
5998
|
return null;
|
|
5690
5999
|
}
|
|
5691
6000
|
const content = generateNeovimConfig(theme, mode);
|
|
5692
|
-
const outputPath =
|
|
6001
|
+
const outputPath = join9(GENERATED_DIR, "neovim.lua");
|
|
5693
6002
|
await writeFile(outputPath, content);
|
|
5694
6003
|
return {
|
|
5695
6004
|
template: {
|
|
@@ -5714,8 +6023,8 @@ async function generateThemeConfigs(theme, mode) {
|
|
|
5714
6023
|
|
|
5715
6024
|
// src/lib/migration/extractor.ts
|
|
5716
6025
|
init_runtime();
|
|
5717
|
-
import { existsSync as
|
|
5718
|
-
import { join as
|
|
6026
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7 } from "fs";
|
|
6027
|
+
import { join as join10 } from "path";
|
|
5719
6028
|
function normalizeHex2(hex) {
|
|
5720
6029
|
hex = hex.replace(/^(#|0x)/i, "");
|
|
5721
6030
|
if (hex.length === 3) {
|
|
@@ -5856,7 +6165,7 @@ async function extractFromGhostty(path) {
|
|
|
5856
6165
|
return { colors: colors5, source: "ghostty" };
|
|
5857
6166
|
}
|
|
5858
6167
|
async function extractColors(filePath) {
|
|
5859
|
-
if (!
|
|
6168
|
+
if (!existsSync11(filePath)) {
|
|
5860
6169
|
return null;
|
|
5861
6170
|
}
|
|
5862
6171
|
const filename = filePath.toLowerCase();
|
|
@@ -5878,7 +6187,7 @@ async function extractColors(filePath) {
|
|
|
5878
6187
|
return null;
|
|
5879
6188
|
}
|
|
5880
6189
|
async function extractFromLegacyTheme(themePath) {
|
|
5881
|
-
if (!
|
|
6190
|
+
if (!existsSync11(themePath)) {
|
|
5882
6191
|
return null;
|
|
5883
6192
|
}
|
|
5884
6193
|
const files = readdirSync7(themePath, { withFileTypes: true });
|
|
@@ -5890,12 +6199,12 @@ async function extractFromLegacyTheme(themePath) {
|
|
|
5890
6199
|
for (const preferred of preferredFiles) {
|
|
5891
6200
|
const match = files.find((f) => f.name.toLowerCase() === preferred.toLowerCase());
|
|
5892
6201
|
if (match) {
|
|
5893
|
-
return extractColors(
|
|
6202
|
+
return extractColors(join10(themePath, match.name));
|
|
5894
6203
|
}
|
|
5895
6204
|
}
|
|
5896
6205
|
for (const file of files) {
|
|
5897
6206
|
if (file.isFile() && (file.name.endsWith(".conf") || file.name.endsWith(".toml"))) {
|
|
5898
|
-
const result = await extractColors(
|
|
6207
|
+
const result = await extractColors(join10(themePath, file.name));
|
|
5899
6208
|
if (result && Object.keys(result.colors).length > 0) {
|
|
5900
6209
|
return result;
|
|
5901
6210
|
}
|
|
@@ -5984,19 +6293,19 @@ function generateThemeJson(name, colors5, options = {}) {
|
|
|
5984
6293
|
init_runtime();
|
|
5985
6294
|
|
|
5986
6295
|
// src/lib/wallpaper.ts
|
|
5987
|
-
import { existsSync as
|
|
5988
|
-
import { join as
|
|
6296
|
+
import { existsSync as existsSync12, readdirSync as readdirSync8, unlinkSync } from "fs";
|
|
6297
|
+
import { join as join11 } from "path";
|
|
5989
6298
|
init_runtime();
|
|
5990
6299
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
5991
6300
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
5992
6301
|
function clearBackgroundsDir() {
|
|
5993
|
-
if (!
|
|
6302
|
+
if (!existsSync12(BACKGROUNDS_TARGET_DIR)) {
|
|
5994
6303
|
return;
|
|
5995
6304
|
}
|
|
5996
6305
|
const entries = readdirSync8(BACKGROUNDS_TARGET_DIR, { withFileTypes: true });
|
|
5997
6306
|
for (const entry of entries) {
|
|
5998
6307
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
5999
|
-
unlinkSync(
|
|
6308
|
+
unlinkSync(join11(BACKGROUNDS_TARGET_DIR, entry.name));
|
|
6000
6309
|
}
|
|
6001
6310
|
}
|
|
6002
6311
|
}
|
|
@@ -6060,7 +6369,7 @@ async function downloadWallpaper(url, filename, timeoutMs = DEFAULT_TIMEOUT_MS)
|
|
|
6060
6369
|
};
|
|
6061
6370
|
}
|
|
6062
6371
|
const ext = getExtension(url, contentType);
|
|
6063
|
-
const outputPath =
|
|
6372
|
+
const outputPath = join11(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
|
|
6064
6373
|
await writeBuffer(outputPath, arrayBuffer);
|
|
6065
6374
|
return { success: true, path: outputPath };
|
|
6066
6375
|
} catch (err) {
|
|
@@ -6117,11 +6426,11 @@ async function listAllThemes() {
|
|
|
6117
6426
|
});
|
|
6118
6427
|
}
|
|
6119
6428
|
}
|
|
6120
|
-
if (
|
|
6429
|
+
if (existsSync13(THEMES_DIR)) {
|
|
6121
6430
|
const entries = readdirSync9(THEMES_DIR, { withFileTypes: true });
|
|
6122
6431
|
for (const entry of entries) {
|
|
6123
6432
|
if (entry.isDirectory()) {
|
|
6124
|
-
const themePath =
|
|
6433
|
+
const themePath = join12(THEMES_DIR, entry.name);
|
|
6125
6434
|
const theme = await parseTheme(themePath, entry.name);
|
|
6126
6435
|
themes.push({
|
|
6127
6436
|
displayName: theme.name,
|
|
@@ -6138,10 +6447,10 @@ async function listAllThemes() {
|
|
|
6138
6447
|
return themes;
|
|
6139
6448
|
}
|
|
6140
6449
|
function clearDirectory(dir) {
|
|
6141
|
-
if (
|
|
6450
|
+
if (existsSync13(dir)) {
|
|
6142
6451
|
const entries = readdirSync9(dir, { withFileTypes: true });
|
|
6143
6452
|
for (const entry of entries) {
|
|
6144
|
-
const fullPath =
|
|
6453
|
+
const fullPath = join12(dir, entry.name);
|
|
6145
6454
|
if (entry.isSymbolicLink() || entry.isFile()) {
|
|
6146
6455
|
unlinkSync2(fullPath);
|
|
6147
6456
|
} else if (entry.isDirectory()) {
|
|
@@ -6151,7 +6460,7 @@ function clearDirectory(dir) {
|
|
|
6151
6460
|
}
|
|
6152
6461
|
}
|
|
6153
6462
|
function createSymlink(source, target) {
|
|
6154
|
-
if (
|
|
6463
|
+
if (existsSync13(target)) {
|
|
6155
6464
|
unlinkSync2(target);
|
|
6156
6465
|
}
|
|
6157
6466
|
symlinkSync(source, target);
|
|
@@ -6175,19 +6484,19 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
|
|
|
6175
6484
|
await installAllTemplates();
|
|
6176
6485
|
}
|
|
6177
6486
|
clearDirectory(THEME_TARGET_DIR);
|
|
6178
|
-
if (
|
|
6487
|
+
if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
|
|
6179
6488
|
rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
|
|
6180
6489
|
}
|
|
6181
6490
|
const results = await generateThemeConfigs(theme, mode);
|
|
6182
|
-
const ghosttyThemesDir =
|
|
6491
|
+
const ghosttyThemesDir = join12(HOME_DIR, ".config", "ghostty", "themes");
|
|
6183
6492
|
for (const result of results) {
|
|
6184
6493
|
const filename = basename4(result.outputPath);
|
|
6185
6494
|
if (filename === "formalconf-dark" || filename === "formalconf-light") {
|
|
6186
6495
|
await ensureDir2(ghosttyThemesDir);
|
|
6187
|
-
const targetPath2 =
|
|
6496
|
+
const targetPath2 = join12(ghosttyThemesDir, filename);
|
|
6188
6497
|
copyFileSync(result.outputPath, targetPath2);
|
|
6189
6498
|
}
|
|
6190
|
-
const targetPath =
|
|
6499
|
+
const targetPath = join12(THEME_TARGET_DIR, filename);
|
|
6191
6500
|
copyFileSync(result.outputPath, targetPath);
|
|
6192
6501
|
}
|
|
6193
6502
|
let wallpaperPaths = [];
|
|
@@ -6197,6 +6506,10 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
|
|
|
6197
6506
|
wallpaperPaths = wallpaperResult.paths;
|
|
6198
6507
|
wallpaperErrors = wallpaperResult.errors;
|
|
6199
6508
|
}
|
|
6509
|
+
let gtkResult = null;
|
|
6510
|
+
if (getOS() === "linux") {
|
|
6511
|
+
gtkResult = await applyGtkTheme(theme, mode);
|
|
6512
|
+
}
|
|
6200
6513
|
if (saveMapping) {
|
|
6201
6514
|
await setDeviceTheme(identifier);
|
|
6202
6515
|
}
|
|
@@ -6223,6 +6536,15 @@ Wallpapers (${wallpaperPaths.length}):`;
|
|
|
6223
6536
|
output += `
|
|
6224
6537
|
Warning: ${error}`;
|
|
6225
6538
|
}
|
|
6539
|
+
if (gtkResult && !gtkResult.skipped) {
|
|
6540
|
+
if (gtkResult.success) {
|
|
6541
|
+
output += `
|
|
6542
|
+
GTK theme: ${gtkResult.themeName}`;
|
|
6543
|
+
} else {
|
|
6544
|
+
output += `
|
|
6545
|
+
Warning: GTK theme failed - ${gtkResult.error}`;
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6226
6548
|
const hookEnv = {
|
|
6227
6549
|
FORMALCONF_THEME: identifier,
|
|
6228
6550
|
FORMALCONF_THEME_MODE: mode,
|
|
@@ -6231,6 +6553,9 @@ Wallpapers (${wallpaperPaths.length}):`;
|
|
|
6231
6553
|
if (wallpaperPaths.length > 0) {
|
|
6232
6554
|
hookEnv.FORMALCONF_WALLPAPER_PATHS = wallpaperPaths.join(":");
|
|
6233
6555
|
}
|
|
6556
|
+
if (gtkResult?.success && gtkResult.themeName) {
|
|
6557
|
+
hookEnv.FORMALCONF_GTK_THEME = gtkResult.themeName;
|
|
6558
|
+
}
|
|
6234
6559
|
const hookSummary = await runHooks("theme-change", hookEnv);
|
|
6235
6560
|
if (hookSummary.executed > 0) {
|
|
6236
6561
|
output += `
|
|
@@ -6245,27 +6570,27 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
|
|
|
6245
6570
|
return { output, success: true };
|
|
6246
6571
|
}
|
|
6247
6572
|
async function applyLegacyTheme(themeName, saveMapping) {
|
|
6248
|
-
const themeDir =
|
|
6249
|
-
if (!
|
|
6573
|
+
const themeDir = join12(THEMES_DIR, themeName);
|
|
6574
|
+
if (!existsSync13(themeDir)) {
|
|
6250
6575
|
return { output: `Theme '${themeName}' not found`, success: false };
|
|
6251
6576
|
}
|
|
6252
6577
|
await ensureConfigDir();
|
|
6253
6578
|
await ensureDir2(THEME_TARGET_DIR);
|
|
6254
6579
|
const theme = await parseTheme(themeDir, themeName);
|
|
6255
6580
|
clearDirectory(THEME_TARGET_DIR);
|
|
6256
|
-
if (
|
|
6581
|
+
if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
|
|
6257
6582
|
rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
|
|
6258
6583
|
}
|
|
6259
6584
|
const entries = readdirSync9(themeDir, { withFileTypes: true });
|
|
6260
6585
|
for (const entry of entries) {
|
|
6261
|
-
const source =
|
|
6586
|
+
const source = join12(themeDir, entry.name);
|
|
6262
6587
|
if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
|
|
6263
|
-
const target =
|
|
6588
|
+
const target = join12(THEME_TARGET_DIR, entry.name);
|
|
6264
6589
|
createSymlink(source, target);
|
|
6265
6590
|
}
|
|
6266
6591
|
}
|
|
6267
6592
|
if (theme.hasBackgrounds) {
|
|
6268
|
-
const backgroundsSource =
|
|
6593
|
+
const backgroundsSource = join12(themeDir, "backgrounds");
|
|
6269
6594
|
createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
|
|
6270
6595
|
}
|
|
6271
6596
|
if (saveMapping) {
|
|
@@ -6305,12 +6630,12 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
|
|
|
6305
6630
|
}
|
|
6306
6631
|
async function applyTheme(themeIdentifier, saveMapping = false) {
|
|
6307
6632
|
const { name, mode } = parseThemeIdentifier(themeIdentifier);
|
|
6308
|
-
const jsonPath =
|
|
6309
|
-
if (
|
|
6633
|
+
const jsonPath = join12(THEMES_DIR, `${name}.json`);
|
|
6634
|
+
if (existsSync13(jsonPath) && mode) {
|
|
6310
6635
|
return applyJsonTheme(jsonPath, mode, saveMapping, themeIdentifier);
|
|
6311
6636
|
}
|
|
6312
|
-
const legacyPath =
|
|
6313
|
-
if (
|
|
6637
|
+
const legacyPath = join12(THEMES_DIR, name);
|
|
6638
|
+
if (existsSync13(legacyPath)) {
|
|
6314
6639
|
return applyLegacyTheme(name, saveMapping);
|
|
6315
6640
|
}
|
|
6316
6641
|
const allThemes = await listAllThemes();
|
|
@@ -6329,8 +6654,8 @@ Did you mean:`;
|
|
|
6329
6654
|
}
|
|
6330
6655
|
async function showThemeInfo(themeIdentifier) {
|
|
6331
6656
|
const { name, mode } = parseThemeIdentifier(themeIdentifier);
|
|
6332
|
-
const jsonPath =
|
|
6333
|
-
if (
|
|
6657
|
+
const jsonPath = join12(THEMES_DIR, `${name}.json`);
|
|
6658
|
+
if (existsSync13(jsonPath)) {
|
|
6334
6659
|
const theme2 = await loadThemeJson(jsonPath);
|
|
6335
6660
|
const modes = getAvailableModes(theme2);
|
|
6336
6661
|
console.log(`
|
|
@@ -6370,8 +6695,8 @@ ${colors5.green}Wallpapers:${colors5.reset}`);
|
|
|
6370
6695
|
}
|
|
6371
6696
|
return;
|
|
6372
6697
|
}
|
|
6373
|
-
const themeDir =
|
|
6374
|
-
if (!
|
|
6698
|
+
const themeDir = join12(THEMES_DIR, name);
|
|
6699
|
+
if (!existsSync13(themeDir)) {
|
|
6375
6700
|
console.error(`${colors5.red}Error: Theme '${themeIdentifier}' not found${colors5.reset}`);
|
|
6376
6701
|
process.exit(1);
|
|
6377
6702
|
}
|
|
@@ -6480,8 +6805,8 @@ To add themes:`);
|
|
|
6480
6805
|
}
|
|
6481
6806
|
}
|
|
6482
6807
|
async function migrateTheme(themeName) {
|
|
6483
|
-
const legacyPath =
|
|
6484
|
-
if (!
|
|
6808
|
+
const legacyPath = join12(THEMES_DIR, themeName);
|
|
6809
|
+
if (!existsSync13(legacyPath)) {
|
|
6485
6810
|
console.error(`${colors5.red}Error: Legacy theme '${themeName}' not found${colors5.reset}`);
|
|
6486
6811
|
process.exit(1);
|
|
6487
6812
|
}
|
|
@@ -6498,13 +6823,13 @@ async function migrateTheme(themeName) {
|
|
|
6498
6823
|
console.log(`${colors5.yellow}Warning: Missing colors will be filled with defaults:${colors5.reset}`);
|
|
6499
6824
|
console.log(` ${missing.join(", ")}`);
|
|
6500
6825
|
}
|
|
6501
|
-
const isLight =
|
|
6826
|
+
const isLight = existsSync13(join12(legacyPath, "light.mode"));
|
|
6502
6827
|
const themeJson = generateThemeJson(themeName, result.colors, {
|
|
6503
6828
|
description: `Migrated from legacy theme`,
|
|
6504
6829
|
isLight
|
|
6505
6830
|
});
|
|
6506
|
-
const outputPath =
|
|
6507
|
-
if (
|
|
6831
|
+
const outputPath = join12(THEMES_DIR, `${themeName}.json`);
|
|
6832
|
+
if (existsSync13(outputPath)) {
|
|
6508
6833
|
console.error(`${colors5.red}Error: JSON theme '${themeName}.json' already exists${colors5.reset}`);
|
|
6509
6834
|
console.error(`Delete or rename it first, then try again.`);
|
|
6510
6835
|
process.exit(1);
|