@heyitsiveen/dotfiles 1.0.4 → 1.1.0
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/README.md +12 -8
- package/dist/index.mjs +44 -1173
- package/dist/prompts-CzEUjeU4.mjs +1195 -0
- package/dotfiles/macos/.claude/settings.json +5 -1
- package/dotfiles/macos/.claude.json +0 -16
- package/dotfiles/windows/.claude/settings.json +5 -1
- package/dotfiles/windows/.claude.json +0 -16
- package/package.json +8 -2
- package/dotfiles/macos/.config/nvim/lazy-lock.json +0 -42
- package/dotfiles/windows/.config/nvim/lazy-lock.json +0 -42
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { log } from "@clack/prompts";
|
|
3
3
|
import { defineCommand, runMain } from "citty";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { basename, dirname, join, relative } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import fse from "fs-extra";
|
|
9
|
-
import {
|
|
9
|
+
import { readdir, stat, writeFile } from "node:fs/promises";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
-
import { execSync } from "node:child_process";
|
|
12
11
|
//#region src/constants.ts
|
|
13
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
13
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
@@ -36,7 +35,7 @@ const ASCII_BANNER = ` ██╗ ██╗███████╗██╗
|
|
|
36
35
|
╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═══╝`;
|
|
37
36
|
//#endregion
|
|
38
37
|
//#region src/installer.ts
|
|
39
|
-
const { copy
|
|
38
|
+
const { copy, ensureDir, pathExists, readJson, remove, writeJson } = fse;
|
|
40
39
|
function copyFilter(src) {
|
|
41
40
|
return !COPY_EXCLUDE.has(basename(src));
|
|
42
41
|
}
|
|
@@ -72,7 +71,7 @@ async function install(options) {
|
|
|
72
71
|
const groupDir = join(home, BACKUP_DIR, group.name);
|
|
73
72
|
let backupName = baseName;
|
|
74
73
|
let counter = 2;
|
|
75
|
-
while (await pathExists
|
|
74
|
+
while (await pathExists(join(groupDir, backupName))) {
|
|
76
75
|
backupName = `${baseName}-${counter}`;
|
|
77
76
|
counter++;
|
|
78
77
|
}
|
|
@@ -80,7 +79,7 @@ async function install(options) {
|
|
|
80
79
|
}
|
|
81
80
|
for (const source of sources) {
|
|
82
81
|
const sourcePath = join(platformDir, source);
|
|
83
|
-
if (!await pathExists
|
|
82
|
+
if (!await pathExists(sourcePath)) {
|
|
84
83
|
result.errors.push({
|
|
85
84
|
file: source,
|
|
86
85
|
error: "Source not found"
|
|
@@ -88,11 +87,11 @@ async function install(options) {
|
|
|
88
87
|
continue;
|
|
89
88
|
}
|
|
90
89
|
const targetPath = isMultiSource ? join(group.target, basename(source)) : group.target;
|
|
91
|
-
if (backup && groupBackupDir && await pathExists
|
|
90
|
+
if (backup && groupBackupDir && await pathExists(targetPath)) {
|
|
92
91
|
const backupTarget = group.extraBackupPaths && group.extraBackupPaths.length > 0 ? join(groupBackupDir, "config") : join(groupBackupDir, basename(source));
|
|
93
92
|
if (!dryRun) try {
|
|
94
|
-
await ensureDir
|
|
95
|
-
await copy
|
|
93
|
+
await ensureDir(groupBackupDir);
|
|
94
|
+
await copy(targetPath, backupTarget, { filter: copyFilter });
|
|
96
95
|
result.backedUp.push(group.name);
|
|
97
96
|
} catch (err) {
|
|
98
97
|
result.errors.push({
|
|
@@ -104,12 +103,12 @@ async function install(options) {
|
|
|
104
103
|
}
|
|
105
104
|
if (!dryRun) try {
|
|
106
105
|
const sourceIsDir = (await stat(sourcePath)).isDirectory();
|
|
107
|
-
if (sourceIsDir) await ensureDir
|
|
108
|
-
else await ensureDir
|
|
109
|
-
if (await pathExists
|
|
106
|
+
if (sourceIsDir) await ensureDir(targetPath);
|
|
107
|
+
else await ensureDir(join(targetPath, ".."));
|
|
108
|
+
if (await pathExists(targetPath)) {
|
|
110
109
|
if (sourceIsDir !== (await stat(targetPath)).isDirectory()) await remove(targetPath);
|
|
111
110
|
}
|
|
112
|
-
await copy
|
|
111
|
+
await copy(sourcePath, targetPath, {
|
|
113
112
|
filter: copyFilter,
|
|
114
113
|
overwrite: true
|
|
115
114
|
});
|
|
@@ -126,11 +125,11 @@ async function install(options) {
|
|
|
126
125
|
else installedFiles.push(`${sourcePath} → ${targetPath}`);
|
|
127
126
|
}
|
|
128
127
|
if (backup && groupBackupDir && group.extraBackupPaths) {
|
|
129
|
-
for (const extra of group.extraBackupPaths) if (await pathExists
|
|
128
|
+
for (const extra of group.extraBackupPaths) if (await pathExists(extra.path)) {
|
|
130
129
|
const extraTarget = join(groupBackupDir, extra.label);
|
|
131
130
|
if (!dryRun) try {
|
|
132
|
-
await ensureDir
|
|
133
|
-
await copy
|
|
131
|
+
await ensureDir(extraTarget);
|
|
132
|
+
await copy(extra.path, extraTarget, { filter: copyFilter });
|
|
134
133
|
} catch (err) {
|
|
135
134
|
result.errors.push({
|
|
136
135
|
file: `backup:${group.name}/${extra.label}`,
|
|
@@ -154,13 +153,18 @@ function getManifestPath() {
|
|
|
154
153
|
}
|
|
155
154
|
async function readManifest() {
|
|
156
155
|
const manifestPath = getManifestPath();
|
|
157
|
-
if (await pathExists
|
|
156
|
+
if (await pathExists(manifestPath)) try {
|
|
158
157
|
return await readJson(manifestPath);
|
|
159
158
|
} catch {
|
|
160
159
|
return null;
|
|
161
160
|
}
|
|
162
161
|
return null;
|
|
163
162
|
}
|
|
163
|
+
async function writeManifestAtomic(manifestPath, manifest) {
|
|
164
|
+
const tmpPath = `${manifestPath}.tmp`;
|
|
165
|
+
await writeJson(tmpPath, manifest, { spaces: 2 });
|
|
166
|
+
await fse.rename(tmpPath, manifestPath);
|
|
167
|
+
}
|
|
164
168
|
async function createManifest(result, options) {
|
|
165
169
|
const manifest = {
|
|
166
170
|
version: VERSION,
|
|
@@ -170,8 +174,8 @@ async function createManifest(result, options) {
|
|
|
170
174
|
groups: result.installedGroups
|
|
171
175
|
};
|
|
172
176
|
const manifestPath = getManifestPath();
|
|
173
|
-
await ensureDir
|
|
174
|
-
await
|
|
177
|
+
await ensureDir(join(manifestPath, ".."));
|
|
178
|
+
await writeManifestAtomic(manifestPath, manifest);
|
|
175
179
|
}
|
|
176
180
|
async function updateManifest(updates) {
|
|
177
181
|
const manifest = await readManifest();
|
|
@@ -180,11 +184,11 @@ async function updateManifest(updates) {
|
|
|
180
184
|
...manifest,
|
|
181
185
|
...updates
|
|
182
186
|
};
|
|
183
|
-
await
|
|
187
|
+
await writeManifestAtomic(getManifestPath(), updated);
|
|
184
188
|
}
|
|
185
189
|
async function deleteManifest() {
|
|
186
190
|
const manifestPath = getManifestPath();
|
|
187
|
-
if (await pathExists
|
|
191
|
+
if (await pathExists(manifestPath)) await remove(manifestPath);
|
|
188
192
|
}
|
|
189
193
|
const TERMINAL_DEFAULTS = {
|
|
190
194
|
WezTerm: {
|
|
@@ -203,9 +207,9 @@ async function uninstallGroups(groups) {
|
|
|
203
207
|
try {
|
|
204
208
|
if (termDefault) {
|
|
205
209
|
const configPath = group.files.find((f) => f.endsWith(termDefault.file));
|
|
206
|
-
if (configPath && await pathExists
|
|
207
|
-
for (const file of group.files) if (file !== configPath && await pathExists
|
|
208
|
-
} else if (await pathExists
|
|
210
|
+
if (configPath && await pathExists(configPath)) await writeFile(configPath, termDefault.content, "utf-8");
|
|
211
|
+
for (const file of group.files) if (file !== configPath && await pathExists(file)) await remove(file);
|
|
212
|
+
} else if (await pathExists(group.target)) await remove(group.target);
|
|
209
213
|
} catch (err) {
|
|
210
214
|
errors.push(`${group.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
211
215
|
}
|
|
@@ -216,7 +220,7 @@ async function uninstallGroups(groups) {
|
|
|
216
220
|
async function findAllBackups() {
|
|
217
221
|
const backupRoot = join(homedir(), BACKUP_DIR);
|
|
218
222
|
const result = /* @__PURE__ */ new Map();
|
|
219
|
-
if (!await pathExists
|
|
223
|
+
if (!await pathExists(backupRoot)) return result;
|
|
220
224
|
const groups = await readdir(backupRoot, { withFileTypes: true });
|
|
221
225
|
for (const group of groups) {
|
|
222
226
|
if (!group.isDirectory()) continue;
|
|
@@ -231,1151 +235,6 @@ async function findAllBackups() {
|
|
|
231
235
|
return result;
|
|
232
236
|
}
|
|
233
237
|
//#endregion
|
|
234
|
-
//#region src/platform.ts
|
|
235
|
-
function getDependencyTools(platform) {
|
|
236
|
-
if (platform === "macos") return [
|
|
237
|
-
{
|
|
238
|
-
name: "Git",
|
|
239
|
-
binary: "git",
|
|
240
|
-
description: "Version control + Neovim plugins",
|
|
241
|
-
installCmd: "brew install git",
|
|
242
|
-
required: true
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
name: "fd",
|
|
246
|
-
binary: "fd",
|
|
247
|
-
description: "File finder used by FZF",
|
|
248
|
-
installCmd: "brew install fd",
|
|
249
|
-
required: false
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
name: "eza",
|
|
253
|
-
binary: "eza",
|
|
254
|
-
description: "Modern ls for FZF tree preview",
|
|
255
|
-
installCmd: "brew install eza",
|
|
256
|
-
required: false
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
name: "fastfetch",
|
|
260
|
-
binary: "fastfetch",
|
|
261
|
-
description: "System info tool",
|
|
262
|
-
installCmd: "brew install fastfetch",
|
|
263
|
-
required: false
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
name: "tree-sitter-cli",
|
|
267
|
-
binary: "tree-sitter",
|
|
268
|
-
description: "LazyVim parser compiler (requires C compiler — included in Xcode CLT)",
|
|
269
|
-
installCmd: "brew install tree-sitter-cli",
|
|
270
|
-
required: true,
|
|
271
|
-
forGroup: "Neovim"
|
|
272
|
-
}
|
|
273
|
-
];
|
|
274
|
-
return [
|
|
275
|
-
{
|
|
276
|
-
name: "Git",
|
|
277
|
-
binary: "git",
|
|
278
|
-
description: "Version control + Neovim plugins",
|
|
279
|
-
installCmd: "winget install Git.Git",
|
|
280
|
-
required: true
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: "fd",
|
|
284
|
-
binary: "fd",
|
|
285
|
-
description: "File finder used by FZF",
|
|
286
|
-
installCmd: "winget install sharkdp.fd",
|
|
287
|
-
required: false
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
name: "eza",
|
|
291
|
-
binary: "eza",
|
|
292
|
-
description: "Modern ls for FZF tree preview",
|
|
293
|
-
installCmd: "winget install eza-community.eza",
|
|
294
|
-
required: false
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
name: "fastfetch",
|
|
298
|
-
binary: "fastfetch",
|
|
299
|
-
description: "System info tool",
|
|
300
|
-
installCmd: "winget install Fastfetch-cli.Fastfetch",
|
|
301
|
-
required: false
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
name: "tree-sitter-cli",
|
|
305
|
-
binary: "tree-sitter",
|
|
306
|
-
description: "LazyVim parser compiler (requires C compiler — VS Build Tools or scoop install gcc)",
|
|
307
|
-
installCmd: "npm i -g tree-sitter-cli",
|
|
308
|
-
required: true,
|
|
309
|
-
forGroup: "Neovim"
|
|
310
|
-
}
|
|
311
|
-
];
|
|
312
|
-
}
|
|
313
|
-
function detectPlatform() {
|
|
314
|
-
switch (process.platform) {
|
|
315
|
-
case "darwin": return "macos";
|
|
316
|
-
case "win32": return "windows";
|
|
317
|
-
default: return null;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
function getHomedir() {
|
|
321
|
-
return homedir();
|
|
322
|
-
}
|
|
323
|
-
function detectTool(binary) {
|
|
324
|
-
try {
|
|
325
|
-
execSync(process.platform === "win32" ? `where.exe ${binary}` : `which ${binary}`, { stdio: "ignore" });
|
|
326
|
-
return true;
|
|
327
|
-
} catch {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function getDotfileGroups(platform) {
|
|
332
|
-
const home = getHomedir();
|
|
333
|
-
const config = join(home, ".config");
|
|
334
|
-
if (platform === "macos") return [
|
|
335
|
-
{
|
|
336
|
-
name: "Fish Shell",
|
|
337
|
-
source: ".config/fish",
|
|
338
|
-
target: join(config, "fish"),
|
|
339
|
-
description: "Config, 8 modules, Tide palettes",
|
|
340
|
-
toolBinary: "fish",
|
|
341
|
-
toolDescription: "Modern shell with autosuggestions",
|
|
342
|
-
installCmd: "brew install fish",
|
|
343
|
-
required: true,
|
|
344
|
-
themeSupport: true
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
name: "Ghostty",
|
|
348
|
-
source: ".config/ghostty",
|
|
349
|
-
target: join(config, "ghostty"),
|
|
350
|
-
description: "Terminal emulator",
|
|
351
|
-
toolBinary: "ghostty",
|
|
352
|
-
toolDescription: "GPU-accelerated terminal emulator",
|
|
353
|
-
installCmd: "brew install --cask ghostty",
|
|
354
|
-
required: true,
|
|
355
|
-
themeSupport: true
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: "WezTerm",
|
|
359
|
-
source: ".config/wezterm",
|
|
360
|
-
target: join(config, "wezterm"),
|
|
361
|
-
description: "Cross-platform terminal",
|
|
362
|
-
toolBinary: "wezterm",
|
|
363
|
-
toolDescription: "Cross-platform terminal emulator",
|
|
364
|
-
installCmd: "brew install --cask wezterm",
|
|
365
|
-
required: true,
|
|
366
|
-
themeSupport: true
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
name: "tmux",
|
|
370
|
-
source: ".config/tmux",
|
|
371
|
-
target: join(config, "tmux"),
|
|
372
|
-
description: "7 config files + keybinds",
|
|
373
|
-
toolBinary: "tmux",
|
|
374
|
-
toolDescription: "Terminal multiplexer",
|
|
375
|
-
installCmd: "brew install tmux",
|
|
376
|
-
required: false,
|
|
377
|
-
themeSupport: true
|
|
378
|
-
},
|
|
379
|
-
{
|
|
380
|
-
name: "Neovim",
|
|
381
|
-
source: ".config/nvim",
|
|
382
|
-
target: join(config, "nvim"),
|
|
383
|
-
description: "LazyVim + solarized-osaka",
|
|
384
|
-
toolBinary: "nvim",
|
|
385
|
-
toolDescription: "Hyperextensible text editor",
|
|
386
|
-
installCmd: "brew install neovim",
|
|
387
|
-
required: true,
|
|
388
|
-
themeSupport: true,
|
|
389
|
-
extraBackupPaths: [{
|
|
390
|
-
label: "data",
|
|
391
|
-
path: join(home, ".local", "share", "nvim")
|
|
392
|
-
}]
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
name: "bat",
|
|
396
|
-
source: ".config/bat",
|
|
397
|
-
target: join(config, "bat"),
|
|
398
|
-
description: "Config + custom themes",
|
|
399
|
-
toolBinary: "bat",
|
|
400
|
-
toolDescription: "Cat clone with syntax highlighting",
|
|
401
|
-
installCmd: "brew install bat",
|
|
402
|
-
required: false,
|
|
403
|
-
themeSupport: true
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
name: "btop",
|
|
407
|
-
source: ".config/btop",
|
|
408
|
-
target: join(config, "btop"),
|
|
409
|
-
description: "System monitor + themes",
|
|
410
|
-
toolBinary: "btop",
|
|
411
|
-
toolDescription: "System resource monitor",
|
|
412
|
-
installCmd: "brew install btop",
|
|
413
|
-
required: false,
|
|
414
|
-
themeSupport: true
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
name: "ripgrep",
|
|
418
|
-
source: ".config/ripgrep",
|
|
419
|
-
target: join(config, "ripgrep"),
|
|
420
|
-
description: "Search config",
|
|
421
|
-
toolBinary: "rg",
|
|
422
|
-
toolDescription: "Fast search tool",
|
|
423
|
-
installCmd: "brew install ripgrep",
|
|
424
|
-
required: false,
|
|
425
|
-
themeSupport: false
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
name: "Claude Code",
|
|
429
|
-
source: [".claude.json", ".claude"],
|
|
430
|
-
target: home,
|
|
431
|
-
description: "MCP servers + settings",
|
|
432
|
-
required: false,
|
|
433
|
-
themeSupport: false
|
|
434
|
-
}
|
|
435
|
-
];
|
|
436
|
-
return [
|
|
437
|
-
{
|
|
438
|
-
name: "PowerShell",
|
|
439
|
-
source: "powershell",
|
|
440
|
-
target: join(home, "Documents", "PowerShell"),
|
|
441
|
-
description: "Profile, 5 modules, 3 functions",
|
|
442
|
-
toolBinary: "pwsh",
|
|
443
|
-
toolDescription: "Modern cross-platform shell",
|
|
444
|
-
installCmd: "winget install Microsoft.PowerShell",
|
|
445
|
-
required: true,
|
|
446
|
-
themeSupport: true
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
name: "oh-my-posh",
|
|
450
|
-
source: ".config/omp-themes",
|
|
451
|
-
target: join(config, "omp-themes"),
|
|
452
|
-
description: "3 TOML prompt themes",
|
|
453
|
-
toolBinary: "oh-my-posh",
|
|
454
|
-
toolDescription: "Prompt theme engine",
|
|
455
|
-
installCmd: "winget install JanDeDobbeleer.OhMyPosh",
|
|
456
|
-
required: false,
|
|
457
|
-
themeSupport: true
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
name: "WezTerm",
|
|
461
|
-
source: ".config/wezterm",
|
|
462
|
-
target: join(config, "wezterm"),
|
|
463
|
-
description: "Terminal config",
|
|
464
|
-
toolBinary: "wezterm",
|
|
465
|
-
toolDescription: "Cross-platform terminal emulator",
|
|
466
|
-
installCmd: "winget install wez.wezterm",
|
|
467
|
-
required: true,
|
|
468
|
-
themeSupport: true
|
|
469
|
-
},
|
|
470
|
-
{
|
|
471
|
-
name: "Neovim",
|
|
472
|
-
source: ".config/nvim",
|
|
473
|
-
target: join(process.env.LOCALAPPDATA || join(home, "AppData", "Local"), "nvim"),
|
|
474
|
-
description: "LazyVim + solarized-osaka",
|
|
475
|
-
toolBinary: "nvim",
|
|
476
|
-
toolDescription: "Hyperextensible text editor",
|
|
477
|
-
installCmd: "winget install Neovim.Neovim",
|
|
478
|
-
required: true,
|
|
479
|
-
themeSupport: true,
|
|
480
|
-
extraBackupPaths: [{
|
|
481
|
-
label: "data",
|
|
482
|
-
path: join(process.env.LOCALAPPDATA || join(home, "AppData", "Local"), "nvim-data")
|
|
483
|
-
}]
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
name: "bat",
|
|
487
|
-
source: ".config/bat",
|
|
488
|
-
target: join(process.env.APPDATA || join(home, "AppData", "Roaming"), "bat"),
|
|
489
|
-
description: "Config + custom themes",
|
|
490
|
-
toolBinary: "bat",
|
|
491
|
-
toolDescription: "Cat clone with syntax highlighting",
|
|
492
|
-
installCmd: "winget install sharkdp.bat",
|
|
493
|
-
required: false,
|
|
494
|
-
themeSupport: true
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
name: "btop",
|
|
498
|
-
source: ".config/btop",
|
|
499
|
-
target: join(process.env.APPDATA || join(home, "AppData", "Roaming"), "btop"),
|
|
500
|
-
description: "System monitor + themes",
|
|
501
|
-
toolBinary: "btop",
|
|
502
|
-
toolDescription: "System resource monitor",
|
|
503
|
-
installCmd: "winget install aristocratos.btop4win",
|
|
504
|
-
required: false,
|
|
505
|
-
themeSupport: true
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
name: "ripgrep",
|
|
509
|
-
source: ".config/ripgrep",
|
|
510
|
-
target: join(config, "ripgrep"),
|
|
511
|
-
description: "Search config",
|
|
512
|
-
toolBinary: "rg",
|
|
513
|
-
toolDescription: "Fast search tool",
|
|
514
|
-
installCmd: "winget install BurntSushi.ripgrep.MSVC",
|
|
515
|
-
required: false,
|
|
516
|
-
themeSupport: false
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
name: "Claude Code",
|
|
520
|
-
source: [".claude.json", ".claude"],
|
|
521
|
-
target: home,
|
|
522
|
-
description: "MCP servers + settings",
|
|
523
|
-
required: false,
|
|
524
|
-
themeSupport: false
|
|
525
|
-
}
|
|
526
|
-
];
|
|
527
|
-
}
|
|
528
|
-
//#endregion
|
|
529
|
-
//#region src/theme.ts
|
|
530
|
-
const { ensureDir, pathExists: pathExists$1 } = fse;
|
|
531
|
-
const ghosttyThemes = {
|
|
532
|
-
"solarized-dark": "Solarized Dark Patched",
|
|
533
|
-
vercel: "Vercel",
|
|
534
|
-
vesper: "Vesper"
|
|
535
|
-
};
|
|
536
|
-
const batThemes = {
|
|
537
|
-
"solarized-dark": "Solarized (dark)",
|
|
538
|
-
vercel: "Vercel",
|
|
539
|
-
vesper: "Vesper"
|
|
540
|
-
};
|
|
541
|
-
const btopThemes = {
|
|
542
|
-
"solarized-dark": "Solarized_Dark",
|
|
543
|
-
vercel: "Vercel",
|
|
544
|
-
vesper: "Vesper"
|
|
545
|
-
};
|
|
546
|
-
const weztermThemes = {
|
|
547
|
-
"solarized-dark": "Solarized Dark (Gogh)",
|
|
548
|
-
vercel: "Vercel",
|
|
549
|
-
vesper: "Vesper"
|
|
550
|
-
};
|
|
551
|
-
const nvimPlugins = {
|
|
552
|
-
"solarized-dark": "craftzdog/solarized-osaka.nvim",
|
|
553
|
-
vercel: "tiesen243/vercel.nvim",
|
|
554
|
-
vesper: "datsfilipe/vesper.nvim"
|
|
555
|
-
};
|
|
556
|
-
const tidePalettes = {
|
|
557
|
-
"solarized-dark": "heyitsiveen",
|
|
558
|
-
vercel: "vercel",
|
|
559
|
-
vesper: "vesper"
|
|
560
|
-
};
|
|
561
|
-
const themeHeaderPatterns = {
|
|
562
|
-
"solarized-dark": "SOLARIZED",
|
|
563
|
-
vercel: "VERCEL",
|
|
564
|
-
vesper: "VESPER"
|
|
565
|
-
};
|
|
566
|
-
async function switchTheme(theme, installedGroups, platform, dryRun) {
|
|
567
|
-
const results = [];
|
|
568
|
-
for (const group of installedGroups) try {
|
|
569
|
-
switch (group.name) {
|
|
570
|
-
case "Ghostty":
|
|
571
|
-
if (!dryRun) await switchGhosttyTheme(group.target, theme);
|
|
572
|
-
results.push(`Ghostty → ${ghosttyThemes[theme]}`);
|
|
573
|
-
break;
|
|
574
|
-
case "bat":
|
|
575
|
-
if (!dryRun) await switchBatTheme(group.target, theme);
|
|
576
|
-
results.push(`bat → ${batThemes[theme]}`);
|
|
577
|
-
break;
|
|
578
|
-
case "btop":
|
|
579
|
-
if (!dryRun) await switchSingleLine(join(group.target, "btop.conf"), /^color_theme = .*$/m, `color_theme = "${btopThemes[theme]}"`);
|
|
580
|
-
results.push(`btop → ${btopThemes[theme]}`);
|
|
581
|
-
break;
|
|
582
|
-
case "WezTerm":
|
|
583
|
-
if (!dryRun) await switchSingleLine(join(group.target, "wezterm.lua"), /^config\.color_scheme = .*$/m, `config.color_scheme = '${weztermThemes[theme]}'`);
|
|
584
|
-
results.push(`WezTerm → ${weztermThemes[theme]}`);
|
|
585
|
-
break;
|
|
586
|
-
case "Neovim":
|
|
587
|
-
if (!dryRun) await switchNeovimTheme(group.target, theme);
|
|
588
|
-
results.push(`Neovim → ${nvimPlugins[theme]}`);
|
|
589
|
-
break;
|
|
590
|
-
case "Fish Shell":
|
|
591
|
-
if (!dryRun) {
|
|
592
|
-
await switchTideTheme(group.target, theme);
|
|
593
|
-
await switchFzfTheme(join(group.target, "conf.d", "40-fzf.fish"), theme, /^set -gx FZF_DEFAULT_OPTS/);
|
|
594
|
-
}
|
|
595
|
-
results.push(`Fish/Tide palette → ${tidePalettes[theme]}`);
|
|
596
|
-
results.push(`FZF → ${theme}`);
|
|
597
|
-
break;
|
|
598
|
-
case "tmux":
|
|
599
|
-
if (!dryRun) await switchTmuxTheme(group.target, theme);
|
|
600
|
-
results.push(`tmux statusbar → ${theme}`);
|
|
601
|
-
break;
|
|
602
|
-
case "PowerShell":
|
|
603
|
-
if (!dryRun) await switchFzfTheme(join(group.target, "modules", "fzf.ps1"), theme, /^\$env:FZF_DEFAULT_OPTS/);
|
|
604
|
-
results.push(`FZF (PowerShell) → ${theme}`);
|
|
605
|
-
break;
|
|
606
|
-
case "oh-my-posh":
|
|
607
|
-
if (!dryRun) await switchOmpTheme(theme);
|
|
608
|
-
results.push(`oh-my-posh → ${theme}`);
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
} catch (err) {
|
|
612
|
-
results.push(`${group.name} — failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
613
|
-
}
|
|
614
|
-
return results;
|
|
615
|
-
}
|
|
616
|
-
async function switchGhosttyTheme(targetDir, theme) {
|
|
617
|
-
const filePath = join(targetDir, "config");
|
|
618
|
-
if (!await pathExists$1(filePath)) return;
|
|
619
|
-
let content = await readFile(filePath, "utf-8");
|
|
620
|
-
content = content.replace(/^theme = .*$/m, `theme = "${ghosttyThemes[theme]}"`);
|
|
621
|
-
if (theme === "solarized-dark") {
|
|
622
|
-
if (!/^background = /m.test(content)) content = content.replace(/^(theme = .*)$/m, "$1\n\n# Window\nbackground = #031219");
|
|
623
|
-
} else content = content.replace(/^background = #031219\n/m, "");
|
|
624
|
-
await writeFile(filePath, content, "utf-8");
|
|
625
|
-
}
|
|
626
|
-
async function switchSingleLine(filePath, pattern, replacement) {
|
|
627
|
-
if (!await pathExists$1(filePath)) return;
|
|
628
|
-
await writeFile(filePath, (await readFile(filePath, "utf-8")).replace(pattern, replacement), "utf-8");
|
|
629
|
-
}
|
|
630
|
-
async function switchBatTheme(targetDir, theme) {
|
|
631
|
-
const filePath = join(targetDir, "config");
|
|
632
|
-
if (!await pathExists$1(filePath)) return;
|
|
633
|
-
let content = await readFile(filePath, "utf-8");
|
|
634
|
-
content = content.replace(/^(--theme=.*)$/m, "# $1");
|
|
635
|
-
const targetValue = batThemes[theme];
|
|
636
|
-
content = content.replace(new RegExp(`^# (--theme="${targetValue.replace(/[()]/g, "\\$&")}")$`, "m"), "$1");
|
|
637
|
-
await writeFile(filePath, content, "utf-8");
|
|
638
|
-
}
|
|
639
|
-
async function switchNeovimTheme(targetDir, theme) {
|
|
640
|
-
const filePath = join(targetDir, "lua", "plugins", "colorscheme.lua");
|
|
641
|
-
if (!await pathExists$1(filePath)) return;
|
|
642
|
-
const content = await readFile(filePath, "utf-8");
|
|
643
|
-
const allPlugins = Object.values(nvimPlugins);
|
|
644
|
-
const pluginPattern = new RegExp(`"(${allPlugins.map((p) => p.replace(/[/.]/g, "\\$&")).join("|")})"`);
|
|
645
|
-
await writeFile(filePath, content.replace(pluginPattern, `"${nvimPlugins[theme]}"`), "utf-8");
|
|
646
|
-
}
|
|
647
|
-
async function switchTideTheme(targetDir, theme) {
|
|
648
|
-
const filePath = join(targetDir, "conf.d", "70-tide.fish");
|
|
649
|
-
if (!await pathExists$1(filePath)) return;
|
|
650
|
-
await writeFile(filePath, (await readFile(filePath, "utf-8")).replace(/^(set -l tide_default_palette )\S+$/m, `$1${tidePalettes[theme]}`), "utf-8");
|
|
651
|
-
}
|
|
652
|
-
async function switchOmpTheme(theme) {
|
|
653
|
-
const themeDir = join(homedir(), MANIFEST_DIR, "oh-my-posh");
|
|
654
|
-
await ensureDir(themeDir);
|
|
655
|
-
await writeFile(join(themeDir, "prompt-theme.txt"), theme, "utf-8");
|
|
656
|
-
}
|
|
657
|
-
async function switchFzfTheme(filePath, theme, commandStart) {
|
|
658
|
-
if (!await pathExists$1(filePath)) return;
|
|
659
|
-
await writeFile(filePath, toggleThemeBlocks(await readFile(filePath, "utf-8"), theme, commandStart), "utf-8");
|
|
660
|
-
}
|
|
661
|
-
async function switchTmuxTheme(targetDir, theme) {
|
|
662
|
-
const filePath = join(targetDir, "statusbar.conf");
|
|
663
|
-
if (!await pathExists$1(filePath)) return;
|
|
664
|
-
await writeFile(filePath, toggleThemeBlocks(await readFile(filePath, "utf-8"), theme, /^(set|setw) -g /), "utf-8");
|
|
665
|
-
}
|
|
666
|
-
function toggleThemeBlocks(content, theme, commandStart) {
|
|
667
|
-
const lines = content.split("\n");
|
|
668
|
-
const result = [];
|
|
669
|
-
const targetHeader = themeHeaderPatterns[theme];
|
|
670
|
-
let currentSection = null;
|
|
671
|
-
let inMultiline = false;
|
|
672
|
-
let inHereString = false;
|
|
673
|
-
for (let i = 0; i < lines.length; i++) {
|
|
674
|
-
const line = lines[i];
|
|
675
|
-
const headerMatch = line.match(/║\s+(\S+)/);
|
|
676
|
-
if (headerMatch) {
|
|
677
|
-
const headerName = headerMatch[1];
|
|
678
|
-
for (const [, pattern] of Object.entries(themeHeaderPatterns)) if (headerName.startsWith(pattern)) {
|
|
679
|
-
currentSection = pattern;
|
|
680
|
-
break;
|
|
681
|
-
}
|
|
682
|
-
if (headerName === "STATUS" || headerName === "WINDOW" || headerName === "FZF") {}
|
|
683
|
-
}
|
|
684
|
-
if (currentSection === null) {
|
|
685
|
-
result.push(line);
|
|
686
|
-
inMultiline = line.replace(/^# /, "").endsWith("\\");
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
689
|
-
const shouldBeActive = currentSection === targetHeader;
|
|
690
|
-
const stripped = line.replace(/^# /, "");
|
|
691
|
-
const isCommented = line !== stripped && line.startsWith("# ");
|
|
692
|
-
const isCommandStart = commandStart.test(stripped);
|
|
693
|
-
if (isCommandStart || inMultiline) if (shouldBeActive && isCommented) result.push(stripped);
|
|
694
|
-
else if (!shouldBeActive && !isCommented && !line.match(/^\s*$/)) result.push(`# ${line}`);
|
|
695
|
-
else result.push(line);
|
|
696
|
-
else result.push(line);
|
|
697
|
-
const contentForState = isCommented ? stripped : line;
|
|
698
|
-
if (isCommandStart || inMultiline) if (!inHereString && contentForState.trimEnd().endsWith("\\")) inMultiline = true;
|
|
699
|
-
else if (contentForState.trim() === "\"@") {
|
|
700
|
-
inMultiline = false;
|
|
701
|
-
inHereString = false;
|
|
702
|
-
} else if (contentForState.includes("@\"")) {
|
|
703
|
-
inMultiline = true;
|
|
704
|
-
inHereString = true;
|
|
705
|
-
} else if (inHereString) inMultiline = true;
|
|
706
|
-
else inMultiline = false;
|
|
707
|
-
}
|
|
708
|
-
return result.join("\n");
|
|
709
|
-
}
|
|
710
|
-
//#endregion
|
|
711
|
-
//#region src/prompts.ts
|
|
712
|
-
const { copy, pathExists } = fse;
|
|
713
|
-
function handleCancel(value) {
|
|
714
|
-
if (isCancel(value)) {
|
|
715
|
-
cancel("Operation cancelled.");
|
|
716
|
-
process.exit(0);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
function showBanner() {
|
|
720
|
-
console.log(pc.cyan(ASCII_BANNER));
|
|
721
|
-
console.log(pc.dim(` v${VERSION}`));
|
|
722
|
-
console.log();
|
|
723
|
-
}
|
|
724
|
-
function showPrerequisites(platform) {
|
|
725
|
-
const items = [{
|
|
726
|
-
name: "Nerd Font",
|
|
727
|
-
description: "required for icons (Tide, oh-my-posh, Neovim)",
|
|
728
|
-
link: "https://www.nerdfonts.com/",
|
|
729
|
-
ok: true
|
|
730
|
-
}];
|
|
731
|
-
if (platform === "macos") items.push({
|
|
732
|
-
name: "Homebrew",
|
|
733
|
-
description: "package manager for macOS",
|
|
734
|
-
link: "https://brew.sh/",
|
|
735
|
-
ok: detectTool("brew")
|
|
736
|
-
});
|
|
737
|
-
else items.push({
|
|
738
|
-
name: "winget",
|
|
739
|
-
description: "package manager for Windows",
|
|
740
|
-
link: "https://aka.ms/getwinget",
|
|
741
|
-
ok: detectTool("winget")
|
|
742
|
-
});
|
|
743
|
-
log.info("Prerequisites:");
|
|
744
|
-
for (const item of items) {
|
|
745
|
-
const marker = item.ok ? pc.green("◆") : pc.yellow("⚠");
|
|
746
|
-
const label = item.ok ? item.name : pc.yellow(item.name);
|
|
747
|
-
log.message(` ${marker} ${pc.bold(label)} — ${item.description}`);
|
|
748
|
-
log.message(` ${pc.cyan(item.link)}`);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
/** Detect tools and show status. Returns missing tools (no install). */
|
|
752
|
-
async function showToolStatus(groups, platform) {
|
|
753
|
-
const allTools = [];
|
|
754
|
-
const groupNames = new Set(groups.map((g) => g.name));
|
|
755
|
-
const subTools = /* @__PURE__ */ new Map();
|
|
756
|
-
for (const dep of getDependencyTools(platform)) if (dep.forGroup) {
|
|
757
|
-
if (!groupNames.has(dep.forGroup)) continue;
|
|
758
|
-
const ok = detectTool(dep.binary);
|
|
759
|
-
if (!subTools.has(dep.forGroup)) subTools.set(dep.forGroup, []);
|
|
760
|
-
subTools.get(dep.forGroup).push({
|
|
761
|
-
...dep,
|
|
762
|
-
ok
|
|
763
|
-
});
|
|
764
|
-
allTools.push(dep);
|
|
765
|
-
} else allTools.push(dep);
|
|
766
|
-
for (const g of groups) if (g.toolBinary && g.installCmd) allTools.push({
|
|
767
|
-
name: g.name,
|
|
768
|
-
binary: g.toolBinary,
|
|
769
|
-
description: g.toolDescription,
|
|
770
|
-
installCmd: g.installCmd,
|
|
771
|
-
required: g.required
|
|
772
|
-
});
|
|
773
|
-
if (allTools.length === 0) return [];
|
|
774
|
-
const forGroupNames = new Set([...subTools.values()].flat().map((t) => t.name));
|
|
775
|
-
const requiredTools = [];
|
|
776
|
-
const optionalTools = [];
|
|
777
|
-
const missing = [];
|
|
778
|
-
for (const t of allTools) {
|
|
779
|
-
const ok = detectTool(t.binary);
|
|
780
|
-
const entry = {
|
|
781
|
-
...t,
|
|
782
|
-
ok
|
|
783
|
-
};
|
|
784
|
-
if (!forGroupNames.has(t.name)) if (t.required) requiredTools.push(entry);
|
|
785
|
-
else optionalTools.push(entry);
|
|
786
|
-
if (!ok) missing.push(t);
|
|
787
|
-
}
|
|
788
|
-
const showTool = (t) => {
|
|
789
|
-
const marker = t.ok ? pc.green("◆") : pc.yellow("⚠");
|
|
790
|
-
const label = t.ok ? t.name : pc.yellow(t.name);
|
|
791
|
-
log.message(` ${marker} ${pc.bold(label)} — ${t.description}`);
|
|
792
|
-
};
|
|
793
|
-
const showSubTool = (t) => {
|
|
794
|
-
const marker = t.ok ? pc.green("◆") : pc.yellow("⚠");
|
|
795
|
-
const label = t.ok ? t.name : pc.yellow(t.name);
|
|
796
|
-
log.message(` ${marker} ${label} — ${pc.dim(t.description)}`);
|
|
797
|
-
};
|
|
798
|
-
log.info("Tools:");
|
|
799
|
-
log.message(` ${pc.bold("Required")}`);
|
|
800
|
-
for (const t of requiredTools) {
|
|
801
|
-
showTool(t);
|
|
802
|
-
const subs = subTools.get(t.name);
|
|
803
|
-
if (subs) for (const st of subs) showSubTool(st);
|
|
804
|
-
}
|
|
805
|
-
log.message("");
|
|
806
|
-
log.message(` ${pc.bold("Optional")}`);
|
|
807
|
-
for (const t of optionalTools) {
|
|
808
|
-
showTool(t);
|
|
809
|
-
const subs = subTools.get(t.name);
|
|
810
|
-
if (subs) for (const st of subs) showSubTool(st);
|
|
811
|
-
}
|
|
812
|
-
if (missing.length === 0) log.success("All tools installed!");
|
|
813
|
-
return missing;
|
|
814
|
-
}
|
|
815
|
-
/** Show multiselect picker for missing tools. Returns tools to install (no execution). */
|
|
816
|
-
async function pickMissingTools(missing) {
|
|
817
|
-
const selectedNames = await multiselect({
|
|
818
|
-
message: "Install missing tools?",
|
|
819
|
-
options: missing.map((t) => ({
|
|
820
|
-
value: t.name,
|
|
821
|
-
label: t.name,
|
|
822
|
-
hint: t.installCmd
|
|
823
|
-
})),
|
|
824
|
-
initialValues: missing.map((t) => t.name),
|
|
825
|
-
required: false
|
|
826
|
-
});
|
|
827
|
-
handleCancel(selectedNames);
|
|
828
|
-
const toInstall = missing.filter((t) => selectedNames.includes(t.name));
|
|
829
|
-
const brewFirst = toInstall.filter((t) => t.name === "Homebrew");
|
|
830
|
-
const rest = toInstall.filter((t) => t.name !== "Homebrew");
|
|
831
|
-
return [...brewFirst, ...rest];
|
|
832
|
-
}
|
|
833
|
-
const groupCategories = {
|
|
834
|
-
"Shell & Terminal": [
|
|
835
|
-
"Fish Shell",
|
|
836
|
-
"PowerShell",
|
|
837
|
-
"Ghostty",
|
|
838
|
-
"WezTerm",
|
|
839
|
-
"tmux"
|
|
840
|
-
],
|
|
841
|
-
Editor: ["Neovim"],
|
|
842
|
-
"CLI Tools": [
|
|
843
|
-
"bat",
|
|
844
|
-
"btop",
|
|
845
|
-
"ripgrep",
|
|
846
|
-
"oh-my-posh"
|
|
847
|
-
],
|
|
848
|
-
Other: ["Claude Code"]
|
|
849
|
-
};
|
|
850
|
-
function showOverview(groups) {
|
|
851
|
-
log.info("Available configurations:");
|
|
852
|
-
for (const [category, names] of Object.entries(groupCategories)) {
|
|
853
|
-
const categoryGroups = groups.filter((g) => names.includes(g.name));
|
|
854
|
-
if (categoryGroups.length === 0) continue;
|
|
855
|
-
log.message(` ${pc.bold(category)}`);
|
|
856
|
-
for (const g of categoryGroups) {
|
|
857
|
-
log.message(` ${pc.green("◆")} ${pc.bold(g.name)} — ${pc.dim(g.description)}`);
|
|
858
|
-
log.message(` ${pc.dim("→")} ${pc.dim(g.target)}`);
|
|
859
|
-
}
|
|
860
|
-
log.message("");
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
function showReloadCommands(groups) {
|
|
864
|
-
const reloads = [];
|
|
865
|
-
for (const g of groups) switch (g.name) {
|
|
866
|
-
case "Fish Shell":
|
|
867
|
-
reloads.push({
|
|
868
|
-
name: "Fish Shell",
|
|
869
|
-
cmd: "exec fish"
|
|
870
|
-
});
|
|
871
|
-
break;
|
|
872
|
-
case "tmux":
|
|
873
|
-
reloads.push({
|
|
874
|
-
name: "tmux",
|
|
875
|
-
cmd: "tmux source ~/.config/tmux/tmux.conf"
|
|
876
|
-
});
|
|
877
|
-
break;
|
|
878
|
-
case "Neovim":
|
|
879
|
-
reloads.push({
|
|
880
|
-
name: "Neovim",
|
|
881
|
-
cmd: "restart nvim, then run :Lazy sync"
|
|
882
|
-
});
|
|
883
|
-
break;
|
|
884
|
-
}
|
|
885
|
-
if (reloads.length > 0) {
|
|
886
|
-
log.info("Reload your configs:");
|
|
887
|
-
for (const r of reloads) log.message(` ${r.name.padEnd(12)} → ${pc.cyan(r.cmd)}`);
|
|
888
|
-
}
|
|
889
|
-
const restartMsg = "Restart your terminal for all changes to take effect.";
|
|
890
|
-
const w = 57;
|
|
891
|
-
log.message([
|
|
892
|
-
"",
|
|
893
|
-
` ${pc.dim("╭" + "─".repeat(w) + "╮")}`,
|
|
894
|
-
` ${pc.dim("│")} ${pc.bold(restartMsg)} ${pc.dim("│")}`,
|
|
895
|
-
` ${pc.dim("╰" + "─".repeat(w) + "╯")}`
|
|
896
|
-
].join("\n"));
|
|
897
|
-
}
|
|
898
|
-
async function resolvePlatform(flagPlatform) {
|
|
899
|
-
if (flagPlatform === "macos" || flagPlatform === "windows") return flagPlatform;
|
|
900
|
-
const detected = detectPlatform();
|
|
901
|
-
if (detected) {
|
|
902
|
-
log.info(`Detected platform: ${pc.bold(detected === "macos" ? "macOS" : "Windows 11")}`);
|
|
903
|
-
return detected;
|
|
904
|
-
}
|
|
905
|
-
if (process.platform === "linux") {
|
|
906
|
-
log.info("Linux detected — macOS configs will be used (most tools are shared).");
|
|
907
|
-
return "macos";
|
|
908
|
-
}
|
|
909
|
-
const chosen = await select({
|
|
910
|
-
message: "Which platform?",
|
|
911
|
-
options: [{
|
|
912
|
-
value: "macos",
|
|
913
|
-
label: "macOS"
|
|
914
|
-
}, {
|
|
915
|
-
value: "windows",
|
|
916
|
-
label: "Windows 11"
|
|
917
|
-
}]
|
|
918
|
-
});
|
|
919
|
-
handleCancel(chosen);
|
|
920
|
-
return chosen;
|
|
921
|
-
}
|
|
922
|
-
async function showUpdateNotification(updatePromise) {
|
|
923
|
-
if (!updatePromise) return;
|
|
924
|
-
const latest = await updatePromise;
|
|
925
|
-
if (!latest) return;
|
|
926
|
-
const msg = `Update available: ${VERSION} → ${latest}`;
|
|
927
|
-
const cmd = "Run: bunx @heyitsiveen/dotfiles@latest";
|
|
928
|
-
const w = Math.max(msg.length, 38) + 4;
|
|
929
|
-
log.message([
|
|
930
|
-
` ${pc.dim("╭" + "─".repeat(w) + "╮")}`,
|
|
931
|
-
` ${pc.dim("│")} ${pc.yellow(msg.padEnd(w - 2))}${pc.dim("│")}`,
|
|
932
|
-
` ${pc.dim("│")} ${pc.cyan(cmd.padEnd(w - 2))}${pc.dim("│")}`,
|
|
933
|
-
` ${pc.dim("╰" + "─".repeat(w) + "╯")}`
|
|
934
|
-
].join("\n"));
|
|
935
|
-
}
|
|
936
|
-
async function firstRunFlow(flagPlatform, dryRun = false, updatePromise) {
|
|
937
|
-
showBanner();
|
|
938
|
-
intro(pc.bold("heyitsiveen"));
|
|
939
|
-
await showUpdateNotification(updatePromise);
|
|
940
|
-
const platform = await resolvePlatform(flagPlatform);
|
|
941
|
-
showPrerequisites(platform);
|
|
942
|
-
const allGroups = getDotfileGroups(platform);
|
|
943
|
-
const missingTools = await showToolStatus(allGroups, platform);
|
|
944
|
-
const toolsToInstall = missingTools.length > 0 ? await pickMissingTools(missingTools) : [];
|
|
945
|
-
showOverview(allGroups);
|
|
946
|
-
const depTools = getDependencyTools(platform);
|
|
947
|
-
const selectedNames = await multiselect({
|
|
948
|
-
message: "Which dotfiles would you like to install?",
|
|
949
|
-
options: allGroups.map((g) => {
|
|
950
|
-
const toolMissing = g.toolBinary && !detectTool(g.toolBinary);
|
|
951
|
-
const missingDeps = depTools.filter((d) => d.forGroup === g.name && !detectTool(d.binary)).map((d) => d.name);
|
|
952
|
-
const warnings = [];
|
|
953
|
-
if (toolMissing) warnings.push("not installed");
|
|
954
|
-
if (missingDeps.length > 0) warnings.push(`${missingDeps.join(", ")} missing`);
|
|
955
|
-
const hint = warnings.length > 0 ? `⚠ ${warnings.join(", ")}` : g.description;
|
|
956
|
-
return {
|
|
957
|
-
value: g.name,
|
|
958
|
-
label: g.name,
|
|
959
|
-
hint
|
|
960
|
-
};
|
|
961
|
-
}),
|
|
962
|
-
initialValues: allGroups.map((g) => g.name),
|
|
963
|
-
required: true
|
|
964
|
-
});
|
|
965
|
-
handleCancel(selectedNames);
|
|
966
|
-
const selectedGroups = allGroups.filter((g) => selectedNames.includes(g.name));
|
|
967
|
-
const theme = await select({
|
|
968
|
-
message: "Which color theme would you like?",
|
|
969
|
-
options: [
|
|
970
|
-
{
|
|
971
|
-
value: "solarized-dark",
|
|
972
|
-
label: "Solarized Dark",
|
|
973
|
-
hint: "default"
|
|
974
|
-
},
|
|
975
|
-
{
|
|
976
|
-
value: "vercel",
|
|
977
|
-
label: "Vercel"
|
|
978
|
-
},
|
|
979
|
-
{
|
|
980
|
-
value: "vesper",
|
|
981
|
-
label: "Vesper"
|
|
982
|
-
}
|
|
983
|
-
]
|
|
984
|
-
});
|
|
985
|
-
handleCancel(theme);
|
|
986
|
-
let shouldBackup = false;
|
|
987
|
-
for (const g of selectedGroups) {
|
|
988
|
-
const sources = Array.isArray(g.source) ? g.source : [g.source];
|
|
989
|
-
const isMultiSource = Array.isArray(g.source);
|
|
990
|
-
for (const source of sources) if (await pathExists(isMultiSource ? join(g.target, source.split("/").pop()) : g.target)) {
|
|
991
|
-
shouldBackup = true;
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
if (shouldBackup) break;
|
|
995
|
-
}
|
|
996
|
-
if (shouldBackup) {
|
|
997
|
-
const doBackup = await confirm({ message: "Existing dotfiles found. Create backup?" });
|
|
998
|
-
handleCancel(doBackup);
|
|
999
|
-
shouldBackup = doBackup;
|
|
1000
|
-
}
|
|
1001
|
-
if (dryRun) log.info(pc.yellow("Dry run — showing planned operations:"));
|
|
1002
|
-
if (toolsToInstall.length > 0) {
|
|
1003
|
-
log.info("Installing tools...");
|
|
1004
|
-
let toolsInstalled = 0;
|
|
1005
|
-
for (const t of toolsToInstall) {
|
|
1006
|
-
log.message(` ${pc.dim("○")} ${t.name}...`);
|
|
1007
|
-
try {
|
|
1008
|
-
execSync(t.installCmd, {
|
|
1009
|
-
stdio: "pipe",
|
|
1010
|
-
encoding: "utf-8",
|
|
1011
|
-
timeout: 3e5
|
|
1012
|
-
});
|
|
1013
|
-
toolsInstalled++;
|
|
1014
|
-
log.message(` ${pc.green("◆")} ${t.name} installed`);
|
|
1015
|
-
} catch (err) {
|
|
1016
|
-
log.message(` ${pc.yellow("⚠")} ${t.name} failed`);
|
|
1017
|
-
log.message(` ${pc.dim(t.installCmd)} — ${pc.dim(err instanceof Error ? err.message : String(err))}`);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
log.message(` ${pc.green("◆")} ${toolsInstalled}/${toolsToInstall.length} tools installed`);
|
|
1021
|
-
}
|
|
1022
|
-
log.info("Installing dotfiles...");
|
|
1023
|
-
const result = await install({
|
|
1024
|
-
platform,
|
|
1025
|
-
selectedGroups,
|
|
1026
|
-
theme,
|
|
1027
|
-
backup: shouldBackup,
|
|
1028
|
-
dryRun
|
|
1029
|
-
});
|
|
1030
|
-
for (const ig of result.installedGroups) log.message(` ${pc.green("◆")} ${ig.name} → ${pc.dim(ig.target)}`);
|
|
1031
|
-
log.message(` ${pc.green("◆")} ${result.installedGroups.length}/${selectedGroups.length} configs installed`);
|
|
1032
|
-
const themeGroups = selectedGroups.filter((g) => g.themeSupport);
|
|
1033
|
-
if (themeGroups.length > 0) {
|
|
1034
|
-
log.info("Applying theme...");
|
|
1035
|
-
const themeResults = await switchTheme(theme, result.installedGroups.filter((ig) => themeGroups.some((sg) => sg.name === ig.name)), platform, dryRun);
|
|
1036
|
-
for (const r of themeResults) log.message(` ${pc.green("◆")} ${r}`);
|
|
1037
|
-
log.message(` ${pc.green("◆")} Theme: ${theme} activated`);
|
|
1038
|
-
}
|
|
1039
|
-
if (result.backedUp.length > 0) {
|
|
1040
|
-
const unique = [...new Set(result.backedUp)];
|
|
1041
|
-
log.info("Backed up existing configs:");
|
|
1042
|
-
for (const name of unique) log.message(` ${pc.green("◆")} ${name}`);
|
|
1043
|
-
log.message(` ${pc.green("◆")} ${unique.length} configs → ${pc.dim("~/.config/heyitsiveen/dotfiles/backup/")}`);
|
|
1044
|
-
}
|
|
1045
|
-
if (!dryRun) await createManifest(result, {
|
|
1046
|
-
platform,
|
|
1047
|
-
selectedGroups,
|
|
1048
|
-
theme,
|
|
1049
|
-
backup: shouldBackup,
|
|
1050
|
-
dryRun
|
|
1051
|
-
});
|
|
1052
|
-
if (result.errors.length > 0) for (const err of result.errors) log.message(` ${pc.yellow("⚠")} ${err.file}: ${pc.dim(err.error)}`);
|
|
1053
|
-
showReloadCommands(selectedGroups);
|
|
1054
|
-
outro("Done! Your dotfiles are installed.");
|
|
1055
|
-
}
|
|
1056
|
-
async function reRunFlow(manifest, flagPlatform, dryRun = false, updatePromise) {
|
|
1057
|
-
showBanner();
|
|
1058
|
-
intro(pc.bold("heyitsiveen"));
|
|
1059
|
-
await showUpdateNotification(updatePromise);
|
|
1060
|
-
showPrerequisites(manifest.platform);
|
|
1061
|
-
log.info(`Existing installation detected (v${manifest.version}, installed ${manifest.installedAt.split("T")[0]})`);
|
|
1062
|
-
const mode = await select({
|
|
1063
|
-
message: "What would you like to do?",
|
|
1064
|
-
options: [
|
|
1065
|
-
{
|
|
1066
|
-
value: "fresh",
|
|
1067
|
-
label: "Fresh install (backup + overwrite all)"
|
|
1068
|
-
},
|
|
1069
|
-
{
|
|
1070
|
-
value: "update",
|
|
1071
|
-
label: "Update (apply changes from new package version)"
|
|
1072
|
-
},
|
|
1073
|
-
{
|
|
1074
|
-
value: "theme",
|
|
1075
|
-
label: "Change theme"
|
|
1076
|
-
},
|
|
1077
|
-
{
|
|
1078
|
-
value: "uninstall",
|
|
1079
|
-
label: "Uninstall"
|
|
1080
|
-
},
|
|
1081
|
-
{
|
|
1082
|
-
value: "restore",
|
|
1083
|
-
label: "Restore from backup"
|
|
1084
|
-
}
|
|
1085
|
-
]
|
|
1086
|
-
});
|
|
1087
|
-
handleCancel(mode);
|
|
1088
|
-
switch (mode) {
|
|
1089
|
-
case "fresh":
|
|
1090
|
-
await firstRunFlow(flagPlatform, dryRun);
|
|
1091
|
-
break;
|
|
1092
|
-
case "update":
|
|
1093
|
-
await updateFlow(manifest, dryRun);
|
|
1094
|
-
break;
|
|
1095
|
-
case "theme":
|
|
1096
|
-
await themeFlow(manifest, dryRun);
|
|
1097
|
-
break;
|
|
1098
|
-
case "uninstall":
|
|
1099
|
-
await uninstallFlow(manifest, dryRun);
|
|
1100
|
-
break;
|
|
1101
|
-
case "restore":
|
|
1102
|
-
await restoreFlow(dryRun);
|
|
1103
|
-
break;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
async function themeFlow(manifest, dryRun = false, flagTheme) {
|
|
1107
|
-
let theme;
|
|
1108
|
-
if (flagTheme && THEMES.includes(flagTheme)) theme = flagTheme;
|
|
1109
|
-
else {
|
|
1110
|
-
if (!flagTheme) {
|
|
1111
|
-
showBanner();
|
|
1112
|
-
intro(pc.bold("heyitsiveen"));
|
|
1113
|
-
}
|
|
1114
|
-
const chosen = await select({
|
|
1115
|
-
message: "Which color theme would you like?",
|
|
1116
|
-
options: [
|
|
1117
|
-
{
|
|
1118
|
-
value: "solarized-dark",
|
|
1119
|
-
label: "Solarized Dark"
|
|
1120
|
-
},
|
|
1121
|
-
{
|
|
1122
|
-
value: "vercel",
|
|
1123
|
-
label: "Vercel"
|
|
1124
|
-
},
|
|
1125
|
-
{
|
|
1126
|
-
value: "vesper",
|
|
1127
|
-
label: "Vesper"
|
|
1128
|
-
}
|
|
1129
|
-
]
|
|
1130
|
-
});
|
|
1131
|
-
handleCancel(chosen);
|
|
1132
|
-
theme = chosen;
|
|
1133
|
-
}
|
|
1134
|
-
const s = spinner();
|
|
1135
|
-
s.start(`Switching theme → ${theme}...`);
|
|
1136
|
-
const results = await switchTheme(theme, manifest.groups, manifest.platform, dryRun);
|
|
1137
|
-
if (!dryRun) await updateManifest({ theme });
|
|
1138
|
-
s.stop("Theme updated!");
|
|
1139
|
-
for (const r of results) log.success(r);
|
|
1140
|
-
outro(`Theme switched to ${theme}. Restart your terminal to apply.`);
|
|
1141
|
-
}
|
|
1142
|
-
async function uninstallFlow(manifest, dryRun = false) {
|
|
1143
|
-
const mode = await select({
|
|
1144
|
-
message: "What would you like to uninstall?",
|
|
1145
|
-
options: [
|
|
1146
|
-
{
|
|
1147
|
-
value: "configs",
|
|
1148
|
-
label: "Configs only",
|
|
1149
|
-
hint: "remove installed dotfiles"
|
|
1150
|
-
},
|
|
1151
|
-
{
|
|
1152
|
-
value: "tools",
|
|
1153
|
-
label: "Tools only",
|
|
1154
|
-
hint: "uninstall via brew/winget"
|
|
1155
|
-
},
|
|
1156
|
-
{
|
|
1157
|
-
value: "both",
|
|
1158
|
-
label: "Both",
|
|
1159
|
-
hint: "remove configs and uninstall tools"
|
|
1160
|
-
}
|
|
1161
|
-
]
|
|
1162
|
-
});
|
|
1163
|
-
handleCancel(mode);
|
|
1164
|
-
const uninstallConfigs = mode === "configs" || mode === "both";
|
|
1165
|
-
const uninstallTools = mode === "tools" || mode === "both";
|
|
1166
|
-
let selectedConfigGroups = manifest.groups;
|
|
1167
|
-
let configNames = manifest.groups.map((g) => g.name);
|
|
1168
|
-
if (uninstallConfigs) {
|
|
1169
|
-
const selected = await multiselect({
|
|
1170
|
-
message: "Which configs to remove?",
|
|
1171
|
-
options: manifest.groups.map((g) => ({
|
|
1172
|
-
value: g.name,
|
|
1173
|
-
label: g.name,
|
|
1174
|
-
hint: g.target
|
|
1175
|
-
})),
|
|
1176
|
-
initialValues: manifest.groups.map((g) => g.name),
|
|
1177
|
-
required: true
|
|
1178
|
-
});
|
|
1179
|
-
handleCancel(selected);
|
|
1180
|
-
configNames = selected;
|
|
1181
|
-
selectedConfigGroups = manifest.groups.filter((g) => configNames.includes(g.name));
|
|
1182
|
-
}
|
|
1183
|
-
let selectedTools = [];
|
|
1184
|
-
if (uninstallTools) {
|
|
1185
|
-
const allGroups = getDotfileGroups(manifest.platform);
|
|
1186
|
-
const allDeps = getDependencyTools(manifest.platform);
|
|
1187
|
-
const toolOptions = [];
|
|
1188
|
-
for (const g of allGroups) if (g.installCmd && g.toolBinary && detectTool(g.toolBinary)) {
|
|
1189
|
-
const cmd = g.installCmd.replace(/\binstall\b/, "uninstall");
|
|
1190
|
-
toolOptions.push({
|
|
1191
|
-
value: {
|
|
1192
|
-
name: g.name,
|
|
1193
|
-
uninstallCmd: cmd
|
|
1194
|
-
},
|
|
1195
|
-
label: g.name,
|
|
1196
|
-
hint: cmd
|
|
1197
|
-
});
|
|
1198
|
-
}
|
|
1199
|
-
for (const d of allDeps) if (d.installCmd && detectTool(d.binary)) {
|
|
1200
|
-
const cmd = d.installCmd.replace(/\binstall\b/, "uninstall");
|
|
1201
|
-
toolOptions.push({
|
|
1202
|
-
value: {
|
|
1203
|
-
name: d.name,
|
|
1204
|
-
uninstallCmd: cmd
|
|
1205
|
-
},
|
|
1206
|
-
label: d.name,
|
|
1207
|
-
hint: cmd
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1210
|
-
if (toolOptions.length === 0) log.info("No installed tools found to uninstall.");
|
|
1211
|
-
else {
|
|
1212
|
-
const selected = await multiselect({
|
|
1213
|
-
message: "Which tools to uninstall?",
|
|
1214
|
-
options: toolOptions,
|
|
1215
|
-
required: false
|
|
1216
|
-
});
|
|
1217
|
-
handleCancel(selected);
|
|
1218
|
-
selectedTools = selected;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
if (dryRun) {
|
|
1222
|
-
log.info(pc.yellow("Dry run — would remove:"));
|
|
1223
|
-
if (uninstallConfigs) for (const g of selectedConfigGroups) log.info(` ${g.name}: ${g.files.length} config files`);
|
|
1224
|
-
for (const t of selectedTools) log.info(` ${t.name}: ${t.uninstallCmd}`);
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
if (uninstallConfigs) {
|
|
1228
|
-
const s = spinner();
|
|
1229
|
-
s.start("Removing configs...");
|
|
1230
|
-
const errors = await uninstallGroups(selectedConfigGroups);
|
|
1231
|
-
s.stop("Configs removed!");
|
|
1232
|
-
for (const g of selectedConfigGroups) log.success(`Removed ${g.name} config`);
|
|
1233
|
-
for (const err of errors) log.warn(err);
|
|
1234
|
-
}
|
|
1235
|
-
if (selectedTools.length > 0) for (const t of selectedTools) {
|
|
1236
|
-
log.message(` ${pc.dim("○")} Uninstalling ${t.name}...`);
|
|
1237
|
-
try {
|
|
1238
|
-
execSync(t.uninstallCmd, {
|
|
1239
|
-
stdio: "pipe",
|
|
1240
|
-
encoding: "utf-8",
|
|
1241
|
-
timeout: 3e5
|
|
1242
|
-
});
|
|
1243
|
-
log.message(` ${pc.green("◆")} ${t.name} uninstalled`);
|
|
1244
|
-
} catch (err) {
|
|
1245
|
-
log.message(` ${pc.yellow("⚠")} ${t.name} failed`);
|
|
1246
|
-
log.message(` ${pc.dim(t.uninstallCmd)} — ${pc.dim(err instanceof Error ? err.message : String(err))}`);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
if (uninstallConfigs) {
|
|
1250
|
-
if ((await findAllBackups()).size > 0) {
|
|
1251
|
-
const doRestore = await confirm({ message: "Restore from backup?" });
|
|
1252
|
-
handleCancel(doRestore);
|
|
1253
|
-
if (doRestore) await restoreFlow(dryRun);
|
|
1254
|
-
}
|
|
1255
|
-
if (configNames.length === manifest.groups.length) await deleteManifest();
|
|
1256
|
-
else await updateManifest({ groups: manifest.groups.filter((g) => !configNames.includes(g.name)) });
|
|
1257
|
-
}
|
|
1258
|
-
outro("Uninstall complete.");
|
|
1259
|
-
}
|
|
1260
|
-
async function updateFlow(manifest, dryRun = false) {
|
|
1261
|
-
log.info(`Installed: v${manifest.version} → Current: v${VERSION}`);
|
|
1262
|
-
const allGroups = getDotfileGroups(manifest.platform);
|
|
1263
|
-
const installedNames = manifest.groups.map((g) => g.name);
|
|
1264
|
-
const updatableGroups = allGroups.filter((g) => installedNames.includes(g.name));
|
|
1265
|
-
if (updatableGroups.length === 0) {
|
|
1266
|
-
log.info("No groups to update.");
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
const selectedNames = await multiselect({
|
|
1270
|
-
message: "Which groups would you like to update?",
|
|
1271
|
-
options: updatableGroups.map((g) => ({
|
|
1272
|
-
value: g.name,
|
|
1273
|
-
label: g.name
|
|
1274
|
-
})),
|
|
1275
|
-
initialValues: updatableGroups.map((g) => g.name),
|
|
1276
|
-
required: true
|
|
1277
|
-
});
|
|
1278
|
-
handleCancel(selectedNames);
|
|
1279
|
-
const selected = updatableGroups.filter((g) => selectedNames.includes(g.name));
|
|
1280
|
-
if (dryRun) {
|
|
1281
|
-
log.info(pc.yellow("Dry run — would update:"));
|
|
1282
|
-
for (const g of selected) log.info(` ${g.name} → ${g.target}`);
|
|
1283
|
-
return;
|
|
1284
|
-
}
|
|
1285
|
-
const s = spinner();
|
|
1286
|
-
s.start("Updating...");
|
|
1287
|
-
const result = await install({
|
|
1288
|
-
platform: manifest.platform,
|
|
1289
|
-
selectedGroups: selected,
|
|
1290
|
-
theme: manifest.theme,
|
|
1291
|
-
backup: true,
|
|
1292
|
-
dryRun: false
|
|
1293
|
-
});
|
|
1294
|
-
const themeGroups = selected.filter((g) => g.themeSupport);
|
|
1295
|
-
if (themeGroups.length > 0) {
|
|
1296
|
-
const themeInstalledGroups = result.installedGroups.filter((ig) => themeGroups.some((sg) => sg.name === ig.name));
|
|
1297
|
-
await switchTheme(manifest.theme, themeInstalledGroups, manifest.platform, false);
|
|
1298
|
-
}
|
|
1299
|
-
await updateManifest({
|
|
1300
|
-
version: VERSION,
|
|
1301
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1302
|
-
groups: result.installedGroups
|
|
1303
|
-
});
|
|
1304
|
-
s.stop("Update complete!");
|
|
1305
|
-
for (const g of result.installedGroups) log.success(`Updated ${g.name} → ${g.target}`);
|
|
1306
|
-
if (result.errors.length > 0) for (const err of result.errors) log.warn(`${err.file}: ${err.error}`);
|
|
1307
|
-
outro("Update complete.");
|
|
1308
|
-
}
|
|
1309
|
-
async function restoreFlow(dryRun = false) {
|
|
1310
|
-
const allBackups = await findAllBackups();
|
|
1311
|
-
if (allBackups.size === 0) {
|
|
1312
|
-
log.info("No backups found. Nothing to restore.");
|
|
1313
|
-
process.exit(0);
|
|
1314
|
-
}
|
|
1315
|
-
const manifest = await readManifest();
|
|
1316
|
-
if (!manifest) log.warn("No installation record found. Restore may overwrite untracked files.");
|
|
1317
|
-
const selectedGroups = await multiselect({
|
|
1318
|
-
message: "Which configs to restore?",
|
|
1319
|
-
options: [...allBackups.entries()].map(([group, entries]) => ({
|
|
1320
|
-
value: group,
|
|
1321
|
-
label: `${group.padEnd(16)} (${entries.length} backup${entries.length > 1 ? "s" : ""})`,
|
|
1322
|
-
hint: `★ latest: ${entries[0].name}`
|
|
1323
|
-
})),
|
|
1324
|
-
initialValues: [...allBackups.keys()],
|
|
1325
|
-
required: true
|
|
1326
|
-
});
|
|
1327
|
-
handleCancel(selectedGroups);
|
|
1328
|
-
const toRestore = [];
|
|
1329
|
-
for (const group of selectedGroups) {
|
|
1330
|
-
const entries = allBackups.get(group);
|
|
1331
|
-
const chosen = await select({
|
|
1332
|
-
message: `Which ${group} backup to restore?`,
|
|
1333
|
-
options: entries.map((e, i) => ({
|
|
1334
|
-
value: e,
|
|
1335
|
-
label: e.name,
|
|
1336
|
-
hint: i === 0 ? "★ latest" : void 0
|
|
1337
|
-
}))
|
|
1338
|
-
});
|
|
1339
|
-
handleCancel(chosen);
|
|
1340
|
-
toRestore.push(chosen);
|
|
1341
|
-
}
|
|
1342
|
-
const doConfirm = await confirm({ message: `Restore ${toRestore.length} config${toRestore.length > 1 ? "s" : ""}?` });
|
|
1343
|
-
handleCancel(doConfirm);
|
|
1344
|
-
if (!doConfirm) return;
|
|
1345
|
-
if (dryRun) {
|
|
1346
|
-
log.info(pc.yellow("Dry run — would restore:"));
|
|
1347
|
-
for (const b of toRestore) log.info(` ${b.group} → ${b.name}`);
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
const s = spinner();
|
|
1351
|
-
s.start("Restoring...");
|
|
1352
|
-
for (const backup of toRestore) {
|
|
1353
|
-
const matchingGroup = manifest?.groups.find((g) => g.name === backup.group);
|
|
1354
|
-
if (matchingGroup) try {
|
|
1355
|
-
const configSubdir = join(backup.path, "config");
|
|
1356
|
-
if (matchingGroup.extraBackupPaths && await pathExists(configSubdir)) {
|
|
1357
|
-
await copy(configSubdir, matchingGroup.target, { overwrite: true });
|
|
1358
|
-
log.success(`Restored ${backup.group} config → ${matchingGroup.target}`);
|
|
1359
|
-
for (const extra of matchingGroup.extraBackupPaths) {
|
|
1360
|
-
const extraSubdir = join(backup.path, extra.label);
|
|
1361
|
-
if (await pathExists(extraSubdir)) {
|
|
1362
|
-
await copy(extraSubdir, extra.path, { overwrite: true });
|
|
1363
|
-
log.success(`Restored ${backup.group} ${extra.label} → ${extra.path}`);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
} else {
|
|
1367
|
-
await copy(backup.path, matchingGroup.target, { overwrite: true });
|
|
1368
|
-
log.success(`Restored ${backup.group} → ${matchingGroup.target}`);
|
|
1369
|
-
}
|
|
1370
|
-
} catch (err) {
|
|
1371
|
-
log.warn(`Failed to restore ${backup.group}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1372
|
-
}
|
|
1373
|
-
else log.warn(`Skipped ${backup.group} — no matching installation record`);
|
|
1374
|
-
}
|
|
1375
|
-
s.stop("Restore complete!");
|
|
1376
|
-
outro("Dotfiles restored from backup.");
|
|
1377
|
-
}
|
|
1378
|
-
//#endregion
|
|
1379
238
|
//#region src/update-check.ts
|
|
1380
239
|
async function checkForUpdate() {
|
|
1381
240
|
try {
|
|
@@ -1392,6 +251,10 @@ async function checkForUpdate() {
|
|
|
1392
251
|
}
|
|
1393
252
|
//#endregion
|
|
1394
253
|
//#region src/index.ts
|
|
254
|
+
if (Number(process.versions.node.split(".")[0]) < 22) {
|
|
255
|
+
process.stderr.write("\n \x1B[33m@heyitsiveen/dotfiles\x1B[0m requires \x1B[1mNode.js 22\x1B[0m or newer.\n You are running Node " + process.versions.node + ".\n\n Upgrade: \x1B[36mhttps://nodejs.org/\x1B[0m (or via nvm / fnm / volta)\n\n");
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
1395
258
|
runMain(defineCommand({
|
|
1396
259
|
meta: {
|
|
1397
260
|
name: PACKAGE_NAME,
|
|
@@ -1429,6 +292,7 @@ runMain(defineCommand({
|
|
|
1429
292
|
const dryRun = args["dry-run"];
|
|
1430
293
|
const manifest = await readManifest();
|
|
1431
294
|
if (args.restore) {
|
|
295
|
+
const { restoreFlow } = await import("./prompts-CzEUjeU4.mjs");
|
|
1432
296
|
await restoreFlow(dryRun);
|
|
1433
297
|
return;
|
|
1434
298
|
}
|
|
@@ -1437,6 +301,7 @@ runMain(defineCommand({
|
|
|
1437
301
|
log.error("No installation found. Run `npx @heyitsiveen/dotfiles` to install.");
|
|
1438
302
|
process.exit(1);
|
|
1439
303
|
}
|
|
304
|
+
const { uninstallFlow } = await import("./prompts-CzEUjeU4.mjs");
|
|
1440
305
|
await uninstallFlow(manifest, dryRun);
|
|
1441
306
|
return;
|
|
1442
307
|
}
|
|
@@ -1459,11 +324,17 @@ runMain(defineCommand({
|
|
|
1459
324
|
log.error("No installation found. Install dotfiles first, then switch theme.");
|
|
1460
325
|
process.exit(1);
|
|
1461
326
|
}
|
|
327
|
+
const { themeFlow } = await import("./prompts-CzEUjeU4.mjs");
|
|
1462
328
|
await themeFlow(manifest, dryRun, args.theme);
|
|
1463
329
|
return;
|
|
1464
330
|
}
|
|
1465
|
-
if (manifest)
|
|
1466
|
-
|
|
331
|
+
if (manifest) {
|
|
332
|
+
const { reRunFlow } = await import("./prompts-CzEUjeU4.mjs");
|
|
333
|
+
await reRunFlow(manifest, args.platform, dryRun, updatePromise);
|
|
334
|
+
} else {
|
|
335
|
+
const { firstRunFlow } = await import("./prompts-CzEUjeU4.mjs");
|
|
336
|
+
await firstRunFlow(args.platform, dryRun, updatePromise);
|
|
337
|
+
}
|
|
1467
338
|
} catch (err) {
|
|
1468
339
|
if (err.code === "ERR_USE_AFTER_CLOSE") process.exit(0);
|
|
1469
340
|
log.error(pc.red(err instanceof Error ? err.message : "An unexpected error occurred."));
|
|
@@ -1472,4 +343,4 @@ runMain(defineCommand({
|
|
|
1472
343
|
}
|
|
1473
344
|
}));
|
|
1474
345
|
//#endregion
|
|
1475
|
-
export {};
|
|
346
|
+
export { readManifest as a, ASCII_BANNER as c, VERSION as d, install as i, MANIFEST_DIR as l, deleteManifest as n, uninstallGroups as o, findAllBackups as r, updateManifest as s, createManifest as t, THEMES as u };
|