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.
Files changed (2) hide show
  1. package/dist/formalconf.js +388 -63
  2. package/package.json +1 -1
@@ -467,7 +467,7 @@ function StatusIndicator({
467
467
  // package.json
468
468
  var package_default = {
469
469
  name: "formalconf",
470
- version: "2.0.10",
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 existsSync12,
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 join11, basename as basename4 } from "path";
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 existsSync7, readFileSync, writeFileSync } from "fs";
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 (!existsSync7(THEME_CONFIG_PATH)) {
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 existsSync8, readdirSync as readdirSync5 } from "fs";
4866
- import { join as join6, basename as basename2 } from "path";
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 hexToRgb(hex) {
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 } = hexToRgb(normalized);
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 (!existsSync8(themePath)) {
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 (!existsSync8(THEMES_DIR)) {
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 = join6(THEMES_DIR, entry.name);
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 join8 } from "path";
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 existsSync9, readdirSync as readdirSync6 } from "fs";
5335
- import { join as join7 } from "path";
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 (!existsSync9(TEMPLATES_MANIFEST_PATH)) {
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 (!existsSync9(BUNDLED_MANIFEST_PATH)) {
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 = join7(BUNDLED_TEMPLATES_DIR, templateName);
5408
- if (!existsSync9(sourcePath)) {
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 = join7(TEMPLATES_DIR, templateName);
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 (!existsSync9(TEMPLATES_DIR)) {
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: join7(TEMPLATES_DIR, entry.name),
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: join8(GENERATED_DIR, templateFile.outputName)
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 = join8(GENERATED_DIR, "neovim.lua");
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 existsSync10, readdirSync as readdirSync7 } from "fs";
5718
- import { join as join9 } from "path";
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 (!existsSync10(filePath)) {
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 (!existsSync10(themePath)) {
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(join9(themePath, match.name));
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(join9(themePath, file.name));
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 existsSync11, readdirSync as readdirSync8, unlinkSync } from "fs";
5988
- import { join as join10 } from "path";
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 (!existsSync11(BACKGROUNDS_TARGET_DIR)) {
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(join10(BACKGROUNDS_TARGET_DIR, entry.name));
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 = join10(BACKGROUNDS_TARGET_DIR, `${filename}.${ext}`);
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 (existsSync12(THEMES_DIR)) {
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 = join11(THEMES_DIR, entry.name);
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 (existsSync12(dir)) {
6450
+ if (existsSync13(dir)) {
6142
6451
  const entries = readdirSync9(dir, { withFileTypes: true });
6143
6452
  for (const entry of entries) {
6144
- const fullPath = join11(dir, entry.name);
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 (existsSync12(target)) {
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 (existsSync12(BACKGROUNDS_TARGET_DIR)) {
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 = join11(HOME_DIR, ".config", "ghostty", "themes");
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 = join11(ghosttyThemesDir, filename);
6496
+ const targetPath2 = join12(ghosttyThemesDir, filename);
6188
6497
  copyFileSync(result.outputPath, targetPath2);
6189
6498
  }
6190
- const targetPath = join11(THEME_TARGET_DIR, filename);
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 = join11(THEMES_DIR, themeName);
6249
- if (!existsSync12(themeDir)) {
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 (existsSync12(BACKGROUNDS_TARGET_DIR)) {
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 = join11(themeDir, entry.name);
6586
+ const source = join12(themeDir, entry.name);
6262
6587
  if (entry.isFile() && entry.name !== "theme.yaml" && entry.name !== "light.mode") {
6263
- const target = join11(THEME_TARGET_DIR, entry.name);
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 = join11(themeDir, "backgrounds");
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 = join11(THEMES_DIR, `${name}.json`);
6309
- if (existsSync12(jsonPath) && mode) {
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 = join11(THEMES_DIR, name);
6313
- if (existsSync12(legacyPath)) {
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 = join11(THEMES_DIR, `${name}.json`);
6333
- if (existsSync12(jsonPath)) {
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 = join11(THEMES_DIR, name);
6374
- if (!existsSync12(themeDir)) {
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 = join11(THEMES_DIR, themeName);
6484
- if (!existsSync12(legacyPath)) {
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 = existsSync12(join11(legacyPath, "light.mode"));
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 = join11(THEMES_DIR, `${themeName}.json`);
6507
- if (existsSync12(outputPath)) {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formalconf",
3
- "version": "2.0.10",
3
+ "version": "2.0.12",
4
4
  "description": "Dotfiles management TUI for macOS - config management, package sync, and theme switching",
5
5
  "type": "module",
6
6
  "main": "./dist/formalconf.js",