formalconf 2.0.9 → 2.0.11
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 +398 -64
- package/package.json +1 -1
package/dist/formalconf.js
CHANGED
|
@@ -199,6 +199,14 @@ async function writeFile(path, content) {
|
|
|
199
199
|
}
|
|
200
200
|
await nodeWriteFile(path, content, "utf-8");
|
|
201
201
|
}
|
|
202
|
+
async function writeBuffer(path, data) {
|
|
203
|
+
if (isBun) {
|
|
204
|
+
await Bun.write(path, data);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
208
|
+
await nodeWriteFile(path, buffer);
|
|
209
|
+
}
|
|
202
210
|
async function ensureDir(path) {
|
|
203
211
|
if (isBun) {
|
|
204
212
|
await Bun.$`mkdir -p ${path}`.quiet();
|
|
@@ -459,7 +467,7 @@ function StatusIndicator({
|
|
|
459
467
|
// package.json
|
|
460
468
|
var package_default = {
|
|
461
469
|
name: "formalconf",
|
|
462
|
-
version: "2.0.
|
|
470
|
+
version: "2.0.11",
|
|
463
471
|
description: "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
|
|
464
472
|
type: "module",
|
|
465
473
|
main: "./dist/formalconf.js",
|
|
@@ -4658,13 +4666,13 @@ function useThemeGrid({
|
|
|
4658
4666
|
import { parseArgs as parseArgs4 } from "util";
|
|
4659
4667
|
import {
|
|
4660
4668
|
readdirSync as readdirSync9,
|
|
4661
|
-
existsSync as
|
|
4669
|
+
existsSync as existsSync13,
|
|
4662
4670
|
rmSync,
|
|
4663
4671
|
symlinkSync,
|
|
4664
4672
|
unlinkSync as unlinkSync2,
|
|
4665
4673
|
copyFileSync
|
|
4666
4674
|
} from "fs";
|
|
4667
|
-
import { join as
|
|
4675
|
+
import { join as join12, basename as basename4 } from "path";
|
|
4668
4676
|
|
|
4669
4677
|
// src/lib/theme-parser.ts
|
|
4670
4678
|
init_runtime();
|
|
@@ -4776,9 +4784,318 @@ async function runHooks(hookType, env = {}) {
|
|
|
4776
4784
|
};
|
|
4777
4785
|
}
|
|
4778
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
|
+
}
|
|
4779
5096
|
// src/lib/theme-config.ts
|
|
4780
5097
|
import { hostname } from "os";
|
|
4781
|
-
import { existsSync as
|
|
5098
|
+
import { existsSync as existsSync8, readFileSync, writeFileSync } from "fs";
|
|
4782
5099
|
var DEFAULT_CONFIG = {
|
|
4783
5100
|
version: 1,
|
|
4784
5101
|
defaultTheme: null,
|
|
@@ -4788,7 +5105,7 @@ function getDeviceHostname() {
|
|
|
4788
5105
|
return hostname();
|
|
4789
5106
|
}
|
|
4790
5107
|
function loadThemeConfig() {
|
|
4791
|
-
if (!
|
|
5108
|
+
if (!existsSync8(THEME_CONFIG_PATH)) {
|
|
4792
5109
|
return { ...DEFAULT_CONFIG, devices: {} };
|
|
4793
5110
|
}
|
|
4794
5111
|
try {
|
|
@@ -4854,8 +5171,8 @@ function getDefaultTheme() {
|
|
|
4854
5171
|
|
|
4855
5172
|
// src/lib/theme-v2/loader.ts
|
|
4856
5173
|
init_runtime();
|
|
4857
|
-
import { existsSync as
|
|
4858
|
-
import { join as
|
|
5174
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5 } from "fs";
|
|
5175
|
+
import { join as join7, basename as basename2 } from "path";
|
|
4859
5176
|
|
|
4860
5177
|
// src/lib/theme-v2/color.ts
|
|
4861
5178
|
function isValidHex(hex) {
|
|
@@ -4873,7 +5190,7 @@ function normalizeHex(hex) {
|
|
|
4873
5190
|
}
|
|
4874
5191
|
return hex.toUpperCase();
|
|
4875
5192
|
}
|
|
4876
|
-
function
|
|
5193
|
+
function hexToRgb2(hex) {
|
|
4877
5194
|
const normalized = normalizeHex(hex);
|
|
4878
5195
|
if (!isValidHex(normalized)) {
|
|
4879
5196
|
throw new Error(`Invalid hex color: ${hex}`);
|
|
@@ -4885,7 +5202,7 @@ function hexToRgb(hex) {
|
|
|
4885
5202
|
}
|
|
4886
5203
|
function hexToColorVariable(hex) {
|
|
4887
5204
|
const normalized = normalizeHex(hex);
|
|
4888
|
-
const { r, g, b } =
|
|
5205
|
+
const { r, g, b } = hexToRgb2(normalized);
|
|
4889
5206
|
return {
|
|
4890
5207
|
hex: normalized,
|
|
4891
5208
|
strip: normalized.slice(1),
|
|
@@ -5134,7 +5451,7 @@ ${validationErrors}`);
|
|
|
5134
5451
|
}
|
|
5135
5452
|
}
|
|
5136
5453
|
async function loadThemeJson(themePath) {
|
|
5137
|
-
if (!
|
|
5454
|
+
if (!existsSync9(themePath)) {
|
|
5138
5455
|
throw new ThemeLoadError(themePath, "file not found");
|
|
5139
5456
|
}
|
|
5140
5457
|
let content;
|
|
@@ -5164,14 +5481,14 @@ function getAvailableModes(theme) {
|
|
|
5164
5481
|
return modes;
|
|
5165
5482
|
}
|
|
5166
5483
|
async function listJsonThemes() {
|
|
5167
|
-
if (!
|
|
5484
|
+
if (!existsSync9(THEMES_DIR)) {
|
|
5168
5485
|
return [];
|
|
5169
5486
|
}
|
|
5170
5487
|
const entries = readdirSync5(THEMES_DIR, { withFileTypes: true });
|
|
5171
5488
|
const themes = [];
|
|
5172
5489
|
for (const entry of entries) {
|
|
5173
5490
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
5174
|
-
const path =
|
|
5491
|
+
const path = join7(THEMES_DIR, entry.name);
|
|
5175
5492
|
try {
|
|
5176
5493
|
const theme = await loadThemeJson(path);
|
|
5177
5494
|
themes.push({
|
|
@@ -5190,7 +5507,7 @@ async function listJsonThemes() {
|
|
|
5190
5507
|
|
|
5191
5508
|
// src/lib/template-engine/engine.ts
|
|
5192
5509
|
init_runtime();
|
|
5193
|
-
import { join as
|
|
5510
|
+
import { join as join9 } from "path";
|
|
5194
5511
|
|
|
5195
5512
|
// src/lib/template-engine/modifiers.ts
|
|
5196
5513
|
var VALID_MODIFIERS = [
|
|
@@ -5323,14 +5640,14 @@ function renderDualModeTemplate(template, contexts) {
|
|
|
5323
5640
|
|
|
5324
5641
|
// src/lib/template-engine/versioning.ts
|
|
5325
5642
|
init_runtime();
|
|
5326
|
-
import { existsSync as
|
|
5327
|
-
import { join as
|
|
5643
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
|
|
5644
|
+
import { join as join8 } from "path";
|
|
5328
5645
|
var DEFAULT_MANIFEST = {
|
|
5329
5646
|
version: 1,
|
|
5330
5647
|
templates: {}
|
|
5331
5648
|
};
|
|
5332
5649
|
async function loadTemplatesManifest() {
|
|
5333
|
-
if (!
|
|
5650
|
+
if (!existsSync10(TEMPLATES_MANIFEST_PATH)) {
|
|
5334
5651
|
return { ...DEFAULT_MANIFEST };
|
|
5335
5652
|
}
|
|
5336
5653
|
try {
|
|
@@ -5345,7 +5662,7 @@ async function saveTemplatesManifest(manifest) {
|
|
|
5345
5662
|
await writeFile(TEMPLATES_MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
5346
5663
|
}
|
|
5347
5664
|
async function loadBundledManifest() {
|
|
5348
|
-
if (!
|
|
5665
|
+
if (!existsSync10(BUNDLED_MANIFEST_PATH)) {
|
|
5349
5666
|
return { version: 1, templates: {} };
|
|
5350
5667
|
}
|
|
5351
5668
|
try {
|
|
@@ -5396,13 +5713,13 @@ async function installTemplate(templateName) {
|
|
|
5396
5713
|
if (!bundledMeta) {
|
|
5397
5714
|
throw new Error(`Template '${templateName}' not found in bundled templates`);
|
|
5398
5715
|
}
|
|
5399
|
-
const sourcePath =
|
|
5400
|
-
if (!
|
|
5716
|
+
const sourcePath = join8(BUNDLED_TEMPLATES_DIR, templateName);
|
|
5717
|
+
if (!existsSync10(sourcePath)) {
|
|
5401
5718
|
throw new Error(`Template file not found: ${sourcePath}`);
|
|
5402
5719
|
}
|
|
5403
5720
|
await ensureDir2(TEMPLATES_DIR);
|
|
5404
5721
|
const content = await readText(sourcePath);
|
|
5405
|
-
const destPath =
|
|
5722
|
+
const destPath = join8(TEMPLATES_DIR, templateName);
|
|
5406
5723
|
await writeFile(destPath, content);
|
|
5407
5724
|
const manifest = await loadTemplatesManifest();
|
|
5408
5725
|
manifest.templates[templateName] = {
|
|
@@ -5464,7 +5781,7 @@ function getOutputFilename(templateName) {
|
|
|
5464
5781
|
return output;
|
|
5465
5782
|
}
|
|
5466
5783
|
async function listInstalledTemplates() {
|
|
5467
|
-
if (!
|
|
5784
|
+
if (!existsSync10(TEMPLATES_DIR)) {
|
|
5468
5785
|
return [];
|
|
5469
5786
|
}
|
|
5470
5787
|
const entries = readdirSync6(TEMPLATES_DIR, { withFileTypes: true });
|
|
@@ -5473,7 +5790,7 @@ async function listInstalledTemplates() {
|
|
|
5473
5790
|
if (entry.isFile() && entry.name.endsWith(".template")) {
|
|
5474
5791
|
templates.push({
|
|
5475
5792
|
name: entry.name,
|
|
5476
|
-
path:
|
|
5793
|
+
path: join8(TEMPLATES_DIR, entry.name),
|
|
5477
5794
|
outputName: getOutputFilename(entry.name),
|
|
5478
5795
|
type: getTemplateType(entry.name),
|
|
5479
5796
|
partialMode: getPartialMode(entry.name)
|
|
@@ -5647,7 +5964,7 @@ async function renderTemplateFile(templateFile, theme, mode) {
|
|
|
5647
5964
|
return {
|
|
5648
5965
|
template: templateFile,
|
|
5649
5966
|
content,
|
|
5650
|
-
outputPath:
|
|
5967
|
+
outputPath: join9(GENERATED_DIR, templateFile.outputName)
|
|
5651
5968
|
};
|
|
5652
5969
|
}
|
|
5653
5970
|
async function renderAllTemplates(theme, mode) {
|
|
@@ -5681,7 +5998,7 @@ async function generateNeovimConfigFile(theme, mode) {
|
|
|
5681
5998
|
return null;
|
|
5682
5999
|
}
|
|
5683
6000
|
const content = generateNeovimConfig(theme, mode);
|
|
5684
|
-
const outputPath =
|
|
6001
|
+
const outputPath = join9(GENERATED_DIR, "neovim.lua");
|
|
5685
6002
|
await writeFile(outputPath, content);
|
|
5686
6003
|
return {
|
|
5687
6004
|
template: {
|
|
@@ -5706,8 +6023,8 @@ async function generateThemeConfigs(theme, mode) {
|
|
|
5706
6023
|
|
|
5707
6024
|
// src/lib/migration/extractor.ts
|
|
5708
6025
|
init_runtime();
|
|
5709
|
-
import { existsSync as
|
|
5710
|
-
import { join as
|
|
6026
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7 } from "fs";
|
|
6027
|
+
import { join as join10 } from "path";
|
|
5711
6028
|
function normalizeHex2(hex) {
|
|
5712
6029
|
hex = hex.replace(/^(#|0x)/i, "");
|
|
5713
6030
|
if (hex.length === 3) {
|
|
@@ -5848,7 +6165,7 @@ async function extractFromGhostty(path) {
|
|
|
5848
6165
|
return { colors: colors5, source: "ghostty" };
|
|
5849
6166
|
}
|
|
5850
6167
|
async function extractColors(filePath) {
|
|
5851
|
-
if (!
|
|
6168
|
+
if (!existsSync11(filePath)) {
|
|
5852
6169
|
return null;
|
|
5853
6170
|
}
|
|
5854
6171
|
const filename = filePath.toLowerCase();
|
|
@@ -5870,7 +6187,7 @@ async function extractColors(filePath) {
|
|
|
5870
6187
|
return null;
|
|
5871
6188
|
}
|
|
5872
6189
|
async function extractFromLegacyTheme(themePath) {
|
|
5873
|
-
if (!
|
|
6190
|
+
if (!existsSync11(themePath)) {
|
|
5874
6191
|
return null;
|
|
5875
6192
|
}
|
|
5876
6193
|
const files = readdirSync7(themePath, { withFileTypes: true });
|
|
@@ -5882,12 +6199,12 @@ async function extractFromLegacyTheme(themePath) {
|
|
|
5882
6199
|
for (const preferred of preferredFiles) {
|
|
5883
6200
|
const match = files.find((f) => f.name.toLowerCase() === preferred.toLowerCase());
|
|
5884
6201
|
if (match) {
|
|
5885
|
-
return extractColors(
|
|
6202
|
+
return extractColors(join10(themePath, match.name));
|
|
5886
6203
|
}
|
|
5887
6204
|
}
|
|
5888
6205
|
for (const file of files) {
|
|
5889
6206
|
if (file.isFile() && (file.name.endsWith(".conf") || file.name.endsWith(".toml"))) {
|
|
5890
|
-
const result = await extractColors(
|
|
6207
|
+
const result = await extractColors(join10(themePath, file.name));
|
|
5891
6208
|
if (result && Object.keys(result.colors).length > 0) {
|
|
5892
6209
|
return result;
|
|
5893
6210
|
}
|
|
@@ -5976,18 +6293,19 @@ function generateThemeJson(name, colors5, options = {}) {
|
|
|
5976
6293
|
init_runtime();
|
|
5977
6294
|
|
|
5978
6295
|
// src/lib/wallpaper.ts
|
|
5979
|
-
import { existsSync as
|
|
5980
|
-
import { join as
|
|
6296
|
+
import { existsSync as existsSync12, readdirSync as readdirSync8, unlinkSync } from "fs";
|
|
6297
|
+
import { join as join11 } from "path";
|
|
6298
|
+
init_runtime();
|
|
5981
6299
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
5982
6300
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
5983
6301
|
function clearBackgroundsDir() {
|
|
5984
|
-
if (!
|
|
6302
|
+
if (!existsSync12(BACKGROUNDS_TARGET_DIR)) {
|
|
5985
6303
|
return;
|
|
5986
6304
|
}
|
|
5987
6305
|
const entries = readdirSync8(BACKGROUNDS_TARGET_DIR, { withFileTypes: true });
|
|
5988
6306
|
for (const entry of entries) {
|
|
5989
6307
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
5990
|
-
unlinkSync(
|
|
6308
|
+
unlinkSync(join11(BACKGROUNDS_TARGET_DIR, entry.name));
|
|
5991
6309
|
}
|
|
5992
6310
|
}
|
|
5993
6311
|
}
|
|
@@ -6051,8 +6369,8 @@ async function downloadWallpaper(url, filename, timeoutMs = DEFAULT_TIMEOUT_MS)
|
|
|
6051
6369
|
};
|
|
6052
6370
|
}
|
|
6053
6371
|
const ext = getExtension(url, contentType);
|
|
6054
|
-
const outputPath =
|
|
6055
|
-
await
|
|
6372
|
+
const outputPath = join11(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
|
|
6373
|
+
await writeBuffer(outputPath, arrayBuffer);
|
|
6056
6374
|
return { success: true, path: outputPath };
|
|
6057
6375
|
} catch (err) {
|
|
6058
6376
|
clearTimeout(timeoutId);
|
|
@@ -6108,11 +6426,11 @@ async function listAllThemes() {
|
|
|
6108
6426
|
});
|
|
6109
6427
|
}
|
|
6110
6428
|
}
|
|
6111
|
-
if (
|
|
6429
|
+
if (existsSync13(THEMES_DIR)) {
|
|
6112
6430
|
const entries = readdirSync9(THEMES_DIR, { withFileTypes: true });
|
|
6113
6431
|
for (const entry of entries) {
|
|
6114
6432
|
if (entry.isDirectory()) {
|
|
6115
|
-
const themePath =
|
|
6433
|
+
const themePath = join12(THEMES_DIR, entry.name);
|
|
6116
6434
|
const theme = await parseTheme(themePath, entry.name);
|
|
6117
6435
|
themes.push({
|
|
6118
6436
|
displayName: theme.name,
|
|
@@ -6129,10 +6447,10 @@ async function listAllThemes() {
|
|
|
6129
6447
|
return themes;
|
|
6130
6448
|
}
|
|
6131
6449
|
function clearDirectory(dir) {
|
|
6132
|
-
if (
|
|
6450
|
+
if (existsSync13(dir)) {
|
|
6133
6451
|
const entries = readdirSync9(dir, { withFileTypes: true });
|
|
6134
6452
|
for (const entry of entries) {
|
|
6135
|
-
const fullPath =
|
|
6453
|
+
const fullPath = join12(dir, entry.name);
|
|
6136
6454
|
if (entry.isSymbolicLink() || entry.isFile()) {
|
|
6137
6455
|
unlinkSync2(fullPath);
|
|
6138
6456
|
} else if (entry.isDirectory()) {
|
|
@@ -6142,7 +6460,7 @@ function clearDirectory(dir) {
|
|
|
6142
6460
|
}
|
|
6143
6461
|
}
|
|
6144
6462
|
function createSymlink(source, target) {
|
|
6145
|
-
if (
|
|
6463
|
+
if (existsSync13(target)) {
|
|
6146
6464
|
unlinkSync2(target);
|
|
6147
6465
|
}
|
|
6148
6466
|
symlinkSync(source, target);
|
|
@@ -6166,19 +6484,19 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
|
|
|
6166
6484
|
await installAllTemplates();
|
|
6167
6485
|
}
|
|
6168
6486
|
clearDirectory(THEME_TARGET_DIR);
|
|
6169
|
-
if (
|
|
6487
|
+
if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
|
|
6170
6488
|
rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
|
|
6171
6489
|
}
|
|
6172
6490
|
const results = await generateThemeConfigs(theme, mode);
|
|
6173
|
-
const ghosttyThemesDir =
|
|
6491
|
+
const ghosttyThemesDir = join12(HOME_DIR, ".config", "ghostty", "themes");
|
|
6174
6492
|
for (const result of results) {
|
|
6175
6493
|
const filename = basename4(result.outputPath);
|
|
6176
6494
|
if (filename === "formalconf-dark" || filename === "formalconf-light") {
|
|
6177
6495
|
await ensureDir2(ghosttyThemesDir);
|
|
6178
|
-
const targetPath2 =
|
|
6496
|
+
const targetPath2 = join12(ghosttyThemesDir, filename);
|
|
6179
6497
|
copyFileSync(result.outputPath, targetPath2);
|
|
6180
6498
|
}
|
|
6181
|
-
const targetPath =
|
|
6499
|
+
const targetPath = join12(THEME_TARGET_DIR, filename);
|
|
6182
6500
|
copyFileSync(result.outputPath, targetPath);
|
|
6183
6501
|
}
|
|
6184
6502
|
let wallpaperPaths = [];
|
|
@@ -6188,6 +6506,10 @@ async function applyJsonTheme(themePath, mode, saveMapping, identifier) {
|
|
|
6188
6506
|
wallpaperPaths = wallpaperResult.paths;
|
|
6189
6507
|
wallpaperErrors = wallpaperResult.errors;
|
|
6190
6508
|
}
|
|
6509
|
+
let gtkResult = null;
|
|
6510
|
+
if (getOS() === "linux") {
|
|
6511
|
+
gtkResult = await applyGtkTheme(theme, mode);
|
|
6512
|
+
}
|
|
6191
6513
|
if (saveMapping) {
|
|
6192
6514
|
await setDeviceTheme(identifier);
|
|
6193
6515
|
}
|
|
@@ -6214,6 +6536,15 @@ Wallpapers (${wallpaperPaths.length}):`;
|
|
|
6214
6536
|
output += `
|
|
6215
6537
|
Warning: ${error}`;
|
|
6216
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
|
+
}
|
|
6217
6548
|
const hookEnv = {
|
|
6218
6549
|
FORMALCONF_THEME: identifier,
|
|
6219
6550
|
FORMALCONF_THEME_MODE: mode,
|
|
@@ -6222,6 +6553,9 @@ Wallpapers (${wallpaperPaths.length}):`;
|
|
|
6222
6553
|
if (wallpaperPaths.length > 0) {
|
|
6223
6554
|
hookEnv.FORMALCONF_WALLPAPER_PATHS = wallpaperPaths.join(":");
|
|
6224
6555
|
}
|
|
6556
|
+
if (gtkResult?.success && gtkResult.themeName) {
|
|
6557
|
+
hookEnv.FORMALCONF_GTK_THEME = gtkResult.themeName;
|
|
6558
|
+
}
|
|
6225
6559
|
const hookSummary = await runHooks("theme-change", hookEnv);
|
|
6226
6560
|
if (hookSummary.executed > 0) {
|
|
6227
6561
|
output += `
|
|
@@ -6236,27 +6570,27 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
|
|
|
6236
6570
|
return { output, success: true };
|
|
6237
6571
|
}
|
|
6238
6572
|
async function applyLegacyTheme(themeName, saveMapping) {
|
|
6239
|
-
const themeDir =
|
|
6240
|
-
if (!
|
|
6573
|
+
const themeDir = join12(THEMES_DIR, themeName);
|
|
6574
|
+
if (!existsSync13(themeDir)) {
|
|
6241
6575
|
return { output: `Theme '${themeName}' not found`, success: false };
|
|
6242
6576
|
}
|
|
6243
6577
|
await ensureConfigDir();
|
|
6244
6578
|
await ensureDir2(THEME_TARGET_DIR);
|
|
6245
6579
|
const theme = await parseTheme(themeDir, themeName);
|
|
6246
6580
|
clearDirectory(THEME_TARGET_DIR);
|
|
6247
|
-
if (
|
|
6581
|
+
if (existsSync13(BACKGROUNDS_TARGET_DIR)) {
|
|
6248
6582
|
rmSync(BACKGROUNDS_TARGET_DIR, { recursive: true, force: true });
|
|
6249
6583
|
}
|
|
6250
6584
|
const entries = readdirSync9(themeDir, { withFileTypes: true });
|
|
6251
6585
|
for (const entry of entries) {
|
|
6252
|
-
const source =
|
|
6586
|
+
const source = join12(themeDir, entry.name);
|
|
6253
6587
|
if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
|
|
6254
|
-
const target =
|
|
6588
|
+
const target = join12(THEME_TARGET_DIR, entry.name);
|
|
6255
6589
|
createSymlink(source, target);
|
|
6256
6590
|
}
|
|
6257
6591
|
}
|
|
6258
6592
|
if (theme.hasBackgrounds) {
|
|
6259
|
-
const backgroundsSource =
|
|
6593
|
+
const backgroundsSource = join12(themeDir, "backgrounds");
|
|
6260
6594
|
createSymlink(backgroundsSource, BACKGROUNDS_TARGET_DIR);
|
|
6261
6595
|
}
|
|
6262
6596
|
if (saveMapping) {
|
|
@@ -6296,12 +6630,12 @@ Hooks: ${hookSummary.succeeded}/${hookSummary.executed} succeeded`;
|
|
|
6296
6630
|
}
|
|
6297
6631
|
async function applyTheme(themeIdentifier, saveMapping = false) {
|
|
6298
6632
|
const { name, mode } = parseThemeIdentifier(themeIdentifier);
|
|
6299
|
-
const jsonPath =
|
|
6300
|
-
if (
|
|
6633
|
+
const jsonPath = join12(THEMES_DIR, `${name}.json`);
|
|
6634
|
+
if (existsSync13(jsonPath) && mode) {
|
|
6301
6635
|
return applyJsonTheme(jsonPath, mode, saveMapping, themeIdentifier);
|
|
6302
6636
|
}
|
|
6303
|
-
const legacyPath =
|
|
6304
|
-
if (
|
|
6637
|
+
const legacyPath = join12(THEMES_DIR, name);
|
|
6638
|
+
if (existsSync13(legacyPath)) {
|
|
6305
6639
|
return applyLegacyTheme(name, saveMapping);
|
|
6306
6640
|
}
|
|
6307
6641
|
const allThemes = await listAllThemes();
|
|
@@ -6320,8 +6654,8 @@ Did you mean:`;
|
|
|
6320
6654
|
}
|
|
6321
6655
|
async function showThemeInfo(themeIdentifier) {
|
|
6322
6656
|
const { name, mode } = parseThemeIdentifier(themeIdentifier);
|
|
6323
|
-
const jsonPath =
|
|
6324
|
-
if (
|
|
6657
|
+
const jsonPath = join12(THEMES_DIR, `${name}.json`);
|
|
6658
|
+
if (existsSync13(jsonPath)) {
|
|
6325
6659
|
const theme2 = await loadThemeJson(jsonPath);
|
|
6326
6660
|
const modes = getAvailableModes(theme2);
|
|
6327
6661
|
console.log(`
|
|
@@ -6361,8 +6695,8 @@ ${colors5.green}Wallpapers:${colors5.reset}`);
|
|
|
6361
6695
|
}
|
|
6362
6696
|
return;
|
|
6363
6697
|
}
|
|
6364
|
-
const themeDir =
|
|
6365
|
-
if (!
|
|
6698
|
+
const themeDir = join12(THEMES_DIR, name);
|
|
6699
|
+
if (!existsSync13(themeDir)) {
|
|
6366
6700
|
console.error(`${colors5.red}Error: Theme '${themeIdentifier}' not found${colors5.reset}`);
|
|
6367
6701
|
process.exit(1);
|
|
6368
6702
|
}
|
|
@@ -6471,8 +6805,8 @@ To add themes:`);
|
|
|
6471
6805
|
}
|
|
6472
6806
|
}
|
|
6473
6807
|
async function migrateTheme(themeName) {
|
|
6474
|
-
const legacyPath =
|
|
6475
|
-
if (!
|
|
6808
|
+
const legacyPath = join12(THEMES_DIR, themeName);
|
|
6809
|
+
if (!existsSync13(legacyPath)) {
|
|
6476
6810
|
console.error(`${colors5.red}Error: Legacy theme '${themeName}' not found${colors5.reset}`);
|
|
6477
6811
|
process.exit(1);
|
|
6478
6812
|
}
|
|
@@ -6489,13 +6823,13 @@ async function migrateTheme(themeName) {
|
|
|
6489
6823
|
console.log(`${colors5.yellow}Warning: Missing colors will be filled with defaults:${colors5.reset}`);
|
|
6490
6824
|
console.log(` ${missing.join(", ")}`);
|
|
6491
6825
|
}
|
|
6492
|
-
const isLight =
|
|
6826
|
+
const isLight = existsSync13(join12(legacyPath, "light.mode"));
|
|
6493
6827
|
const themeJson = generateThemeJson(themeName, result.colors, {
|
|
6494
6828
|
description: `Migrated from legacy theme`,
|
|
6495
6829
|
isLight
|
|
6496
6830
|
});
|
|
6497
|
-
const outputPath =
|
|
6498
|
-
if (
|
|
6831
|
+
const outputPath = join12(THEMES_DIR, `${themeName}.json`);
|
|
6832
|
+
if (existsSync13(outputPath)) {
|
|
6499
6833
|
console.error(`${colors5.red}Error: JSON theme '${themeName}.json' already exists${colors5.reset}`);
|
|
6500
6834
|
console.error(`Delete or rename it first, then try again.`);
|
|
6501
6835
|
process.exit(1);
|