@m0x373/pimpt 0.0.0-stage → 0.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/dist/index.js +1628 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -4
- package/README.md +0 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,1628 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5
|
+
import pc2 from "picocolors";
|
|
6
|
+
|
|
7
|
+
// src/installer.ts
|
|
8
|
+
import {
|
|
9
|
+
cancel,
|
|
10
|
+
confirm,
|
|
11
|
+
intro,
|
|
12
|
+
isCancel,
|
|
13
|
+
log,
|
|
14
|
+
note,
|
|
15
|
+
outro,
|
|
16
|
+
select,
|
|
17
|
+
spinner,
|
|
18
|
+
text
|
|
19
|
+
} from "@clack/prompts";
|
|
20
|
+
import { spawn } from "child_process";
|
|
21
|
+
import { statSync } from "fs";
|
|
22
|
+
import { parse as parse2 } from "jsonc-parser";
|
|
23
|
+
import { win32 as pathWin32 } from "path";
|
|
24
|
+
import pc from "picocolors";
|
|
25
|
+
import { setTimeout as delay } from "timers/promises";
|
|
26
|
+
|
|
27
|
+
// src/settings.ts
|
|
28
|
+
var BACKGROUND_CUSTOM = "custom";
|
|
29
|
+
var transparencyOptions = [
|
|
30
|
+
{ value: "off", label: "Off", hint: "Solid background" },
|
|
31
|
+
{ value: "90", label: "Subtle", hint: "90% opacity" },
|
|
32
|
+
{ value: "80", label: "Balanced", hint: "80% opacity" },
|
|
33
|
+
{ value: "70", label: "Frosted", hint: "70% opacity" },
|
|
34
|
+
{ value: "50", label: "Heavy", hint: "50% opacity" },
|
|
35
|
+
{ value: "0", label: "Fully transparent", hint: "Content 0%; tab row 50% acrylic" }
|
|
36
|
+
];
|
|
37
|
+
var backgroundOptions = [
|
|
38
|
+
{ value: "keep", label: "Keep current", hint: "Leave the background unchanged" },
|
|
39
|
+
{ value: "#0B1021", label: "Midnight", hint: "#0B1021" },
|
|
40
|
+
{ value: "#2E3440", label: "Nord", hint: "#2E3440" },
|
|
41
|
+
{ value: "#282A36", label: "Dracula", hint: "#282A36" },
|
|
42
|
+
{ value: "#002B36", label: "Solarized Dark", hint: "#002B36" },
|
|
43
|
+
{
|
|
44
|
+
value: BACKGROUND_CUSTOM,
|
|
45
|
+
label: "Custom hex",
|
|
46
|
+
hint: "Enter your own color"
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
var fontOptions = [
|
|
50
|
+
{ value: "keep", label: "Keep current", hint: "Use the configured font" },
|
|
51
|
+
{ value: "Cascadia Mono", label: "Cascadia Mono", hint: "Windows Terminal default" },
|
|
52
|
+
{ value: "Cascadia Code", label: "Cascadia Code", hint: "Ligatures included" },
|
|
53
|
+
{ value: "Consolas", label: "Consolas", hint: "Classic Windows font" },
|
|
54
|
+
{ value: "JetBrains Mono", label: "JetBrains Mono", hint: "Popular developer font" },
|
|
55
|
+
{ value: "Fira Code", label: "Fira Code", hint: "Monospace with ligatures" },
|
|
56
|
+
{ value: "Hack", label: "Hack", hint: "Designed for source code" },
|
|
57
|
+
{ value: "Source Code Pro", label: "Source Code Pro", hint: "Adobe's monospace font" },
|
|
58
|
+
{ value: "IBM Plex Mono", label: "IBM Plex Mono", hint: "Technical, readable monospace" }
|
|
59
|
+
];
|
|
60
|
+
var fontSizeOptions = ["10", "11", "12", "14", "16", "18"].map(
|
|
61
|
+
(value) => ({ value, label: `${value} pt`, hint: "Points" })
|
|
62
|
+
);
|
|
63
|
+
var cursorOptions = [
|
|
64
|
+
{ value: "keep", label: "Keep current", hint: "Use the configured cursor" },
|
|
65
|
+
{ value: "bar", label: "Bar", hint: "Thin vertical line" },
|
|
66
|
+
{ value: "filledBox", label: "Block", hint: "Filled rectangle" },
|
|
67
|
+
{ value: "underscore", label: "Underline", hint: "Line below the text" },
|
|
68
|
+
{ value: "vintage", label: "Vintage", hint: "Retro terminal blink" }
|
|
69
|
+
];
|
|
70
|
+
var schemeOptions = [
|
|
71
|
+
{ value: "keep", label: "Keep current", hint: "Use the configured scheme" },
|
|
72
|
+
{ value: "Campbell", label: "Campbell", hint: "Windows Terminal default" },
|
|
73
|
+
{ value: "One Half Dark", label: "One Half Dark", hint: "Cool, muted palette" },
|
|
74
|
+
{ value: "Solarized Dark", label: "Solarized Dark", hint: "Low-contrast classic" },
|
|
75
|
+
{ value: "Tango Dark", label: "Tango Dark", hint: "Colorful and warm" },
|
|
76
|
+
{ value: "Vintage", label: "Vintage", hint: "Retro phosphor look" }
|
|
77
|
+
];
|
|
78
|
+
var launchModeOptions = [
|
|
79
|
+
{ value: "default", label: "Windowed", hint: "Normal window" },
|
|
80
|
+
{ value: "maximized", label: "Maximized", hint: "Fill the screen" },
|
|
81
|
+
{ value: "fullscreen", label: "Fullscreen", hint: "Hide the window frame" },
|
|
82
|
+
{ value: "focus", label: "Focus mode", hint: "Minimal distractions" },
|
|
83
|
+
{
|
|
84
|
+
value: "maximizedFocus",
|
|
85
|
+
label: "Maximized focus",
|
|
86
|
+
hint: "Maximized and distraction-free"
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
var appThemeOptions = [
|
|
90
|
+
{ value: "system", label: "System", hint: "Follow Windows" },
|
|
91
|
+
{ value: "dark", label: "Dark", hint: "Always dark" },
|
|
92
|
+
{ value: "light", label: "Light", hint: "Always light" }
|
|
93
|
+
];
|
|
94
|
+
var tabLayoutOptions = [
|
|
95
|
+
{ value: "titlebar", label: "Tabs in titlebar", hint: "Compact, modern layout" },
|
|
96
|
+
{ value: "separate", label: "Separate tab row", hint: "Tabs below the titlebar" }
|
|
97
|
+
];
|
|
98
|
+
var tabWidthOptions = [
|
|
99
|
+
{ value: "equal", label: "Equal", hint: "All tabs share the width" },
|
|
100
|
+
{ value: "titleLength", label: "Title length", hint: "Width fits the title" },
|
|
101
|
+
{ value: "compact", label: "Compact", hint: "Shrink to fit" }
|
|
102
|
+
];
|
|
103
|
+
var iconOptions = [
|
|
104
|
+
{ value: "classic", label: "Classic terminal", hint: "Monochrome prompt glyph" },
|
|
105
|
+
{ value: "rocket", label: "Rocket", hint: "Launch-ready" },
|
|
106
|
+
{ value: "bolt", label: "Bolt", hint: "Fast and loud" },
|
|
107
|
+
{ value: "code", label: "Code", hint: "Developer vibes" }
|
|
108
|
+
];
|
|
109
|
+
function labelOf(options, value) {
|
|
110
|
+
return options.find((option) => option.value === value)?.label ?? value;
|
|
111
|
+
}
|
|
112
|
+
function onOff(value) {
|
|
113
|
+
return value === true ? "Enabled" : "Disabled";
|
|
114
|
+
}
|
|
115
|
+
var settings = [
|
|
116
|
+
{
|
|
117
|
+
id: "transparency",
|
|
118
|
+
group: "Appearance",
|
|
119
|
+
label: "Transparency",
|
|
120
|
+
hint: "Window opacity",
|
|
121
|
+
prompt: {
|
|
122
|
+
kind: "select",
|
|
123
|
+
message: "How transparent should the window be?",
|
|
124
|
+
options: transparencyOptions,
|
|
125
|
+
initialValue: "80"
|
|
126
|
+
},
|
|
127
|
+
summarize: (value) => {
|
|
128
|
+
if (value === "off") return "Off";
|
|
129
|
+
if (value === "0") return "Fully transparent content";
|
|
130
|
+
return `Acrylic \xB7 ${String(value)}% opacity`;
|
|
131
|
+
},
|
|
132
|
+
jsonPath: ["profiles", "defaults", "opacity"]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "background",
|
|
136
|
+
group: "Appearance",
|
|
137
|
+
label: "Background color",
|
|
138
|
+
hint: "Preset or custom color",
|
|
139
|
+
prompt: {
|
|
140
|
+
kind: "select",
|
|
141
|
+
message: "Pick a background color",
|
|
142
|
+
options: backgroundOptions,
|
|
143
|
+
initialValue: "keep"
|
|
144
|
+
},
|
|
145
|
+
summarize: (value) => {
|
|
146
|
+
if (value === "keep") return "Keep current";
|
|
147
|
+
const preset = backgroundOptions.find(
|
|
148
|
+
(option) => option.value === value && option.value !== "keep" && option.value !== BACKGROUND_CUSTOM
|
|
149
|
+
);
|
|
150
|
+
return preset ? `${preset.label} (${preset.value})` : `Custom (${String(value)})`;
|
|
151
|
+
},
|
|
152
|
+
jsonPath: ["profiles", "defaults", "background"],
|
|
153
|
+
skipIfKeep: true
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "backgroundImage",
|
|
157
|
+
group: "Appearance",
|
|
158
|
+
label: "Background image",
|
|
159
|
+
hint: "Path to a local image",
|
|
160
|
+
prompt: {
|
|
161
|
+
kind: "text",
|
|
162
|
+
message: "Path to a background image (leave empty to skip)",
|
|
163
|
+
placeholder: "C:\\Users\\you\\wallpaper.png",
|
|
164
|
+
initialValue: ""
|
|
165
|
+
},
|
|
166
|
+
summarize: (value) => String(value).trim() ? String(value) : "Not set"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "fontFace",
|
|
170
|
+
group: "Appearance",
|
|
171
|
+
label: "Font",
|
|
172
|
+
hint: "Terminal font",
|
|
173
|
+
prompt: {
|
|
174
|
+
kind: "select",
|
|
175
|
+
message: "Choose a font",
|
|
176
|
+
options: fontOptions,
|
|
177
|
+
initialValue: "keep"
|
|
178
|
+
},
|
|
179
|
+
summarize: (value) => labelOf(fontOptions, String(value)),
|
|
180
|
+
jsonPath: ["profiles", "defaults", "font", "face"],
|
|
181
|
+
skipIfKeep: true
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "fontSize",
|
|
185
|
+
group: "Appearance",
|
|
186
|
+
label: "Font size",
|
|
187
|
+
hint: "Text size in points",
|
|
188
|
+
prompt: {
|
|
189
|
+
kind: "select",
|
|
190
|
+
message: "Choose a font size",
|
|
191
|
+
options: fontSizeOptions,
|
|
192
|
+
initialValue: "12"
|
|
193
|
+
},
|
|
194
|
+
summarize: (value) => `${String(value)} pt`
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "cursorShape",
|
|
198
|
+
group: "Appearance",
|
|
199
|
+
label: "Cursor",
|
|
200
|
+
hint: "Shape of the text cursor",
|
|
201
|
+
prompt: {
|
|
202
|
+
kind: "select",
|
|
203
|
+
message: "Choose a cursor shape",
|
|
204
|
+
options: cursorOptions,
|
|
205
|
+
initialValue: "keep"
|
|
206
|
+
},
|
|
207
|
+
summarize: (value) => labelOf(cursorOptions, String(value))
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "colorScheme",
|
|
211
|
+
group: "Colors",
|
|
212
|
+
label: "Color scheme",
|
|
213
|
+
hint: "ANSI palette for text and accents",
|
|
214
|
+
prompt: {
|
|
215
|
+
kind: "select",
|
|
216
|
+
message: "Choose a color scheme",
|
|
217
|
+
options: schemeOptions,
|
|
218
|
+
initialValue: "keep"
|
|
219
|
+
},
|
|
220
|
+
summarize: (value) => labelOf(schemeOptions, String(value))
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "launchMode",
|
|
224
|
+
group: "Window & tabs",
|
|
225
|
+
label: "Launch mode",
|
|
226
|
+
hint: "How the window opens",
|
|
227
|
+
prompt: {
|
|
228
|
+
kind: "select",
|
|
229
|
+
message: "How should Windows Terminal open?",
|
|
230
|
+
options: launchModeOptions,
|
|
231
|
+
initialValue: "default"
|
|
232
|
+
},
|
|
233
|
+
summarize: (value) => labelOf(launchModeOptions, String(value))
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "appTheme",
|
|
237
|
+
group: "Window & tabs",
|
|
238
|
+
label: "Application theme",
|
|
239
|
+
hint: "Light, dark, or follow Windows",
|
|
240
|
+
prompt: {
|
|
241
|
+
kind: "select",
|
|
242
|
+
message: "Choose the application theme",
|
|
243
|
+
options: appThemeOptions,
|
|
244
|
+
initialValue: "system"
|
|
245
|
+
},
|
|
246
|
+
summarize: (value) => labelOf(appThemeOptions, String(value))
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "tabLayout",
|
|
250
|
+
group: "Window & tabs",
|
|
251
|
+
label: "Tab bar",
|
|
252
|
+
hint: "Where the tab row is displayed",
|
|
253
|
+
prompt: {
|
|
254
|
+
kind: "select",
|
|
255
|
+
message: "Where should the tab bar live?",
|
|
256
|
+
options: tabLayoutOptions,
|
|
257
|
+
initialValue: "titlebar"
|
|
258
|
+
},
|
|
259
|
+
summarize: (value) => labelOf(tabLayoutOptions, String(value))
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "tabWidthMode",
|
|
263
|
+
group: "Window & tabs",
|
|
264
|
+
label: "Tab width",
|
|
265
|
+
hint: "How tab widths are calculated",
|
|
266
|
+
prompt: {
|
|
267
|
+
kind: "select",
|
|
268
|
+
message: "How should tabs be sized?",
|
|
269
|
+
options: tabWidthOptions,
|
|
270
|
+
initialValue: "equal"
|
|
271
|
+
},
|
|
272
|
+
summarize: (value) => labelOf(tabWidthOptions, String(value))
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: "icon",
|
|
276
|
+
group: "Profile",
|
|
277
|
+
label: "Profile icon",
|
|
278
|
+
hint: "Icon style for your profile",
|
|
279
|
+
prompt: {
|
|
280
|
+
kind: "select",
|
|
281
|
+
message: "Pick a profile icon style",
|
|
282
|
+
options: iconOptions,
|
|
283
|
+
initialValue: "classic"
|
|
284
|
+
},
|
|
285
|
+
summarize: (value) => labelOf(iconOptions, String(value))
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: "tabTitle",
|
|
289
|
+
group: "Profile",
|
|
290
|
+
label: "Tab title",
|
|
291
|
+
hint: "Custom title shown on the tab",
|
|
292
|
+
prompt: {
|
|
293
|
+
kind: "text",
|
|
294
|
+
message: "Custom tab title (leave empty for automatic)",
|
|
295
|
+
placeholder: "pimpt",
|
|
296
|
+
initialValue: ""
|
|
297
|
+
},
|
|
298
|
+
summarize: (value) => String(value).trim() ? String(value) : "Automatic"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "startingDirectory",
|
|
302
|
+
group: "Features",
|
|
303
|
+
label: "Starting directory",
|
|
304
|
+
hint: "Default shell folder",
|
|
305
|
+
prompt: {
|
|
306
|
+
kind: "text",
|
|
307
|
+
message: "Enter or paste a path; relative paths start in your user folder",
|
|
308
|
+
placeholder: "C:\\Users\\you\\Projects",
|
|
309
|
+
initialValue: ""
|
|
310
|
+
},
|
|
311
|
+
summarize: (value) => String(value).trim() ? String(value) : "Default",
|
|
312
|
+
jsonPath: ["profiles", "defaults", "startingDirectory"]
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: "copyOnSelect",
|
|
316
|
+
group: "Behavior",
|
|
317
|
+
label: "Copy on select",
|
|
318
|
+
hint: "Copy text as soon as it is selected",
|
|
319
|
+
prompt: {
|
|
320
|
+
kind: "confirm",
|
|
321
|
+
message: "Copy text automatically when selected?",
|
|
322
|
+
initialValue: false
|
|
323
|
+
},
|
|
324
|
+
summarize: onOff
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "confirmClose",
|
|
328
|
+
group: "Behavior",
|
|
329
|
+
label: "Confirm close",
|
|
330
|
+
hint: "Ask before closing multiple tabs",
|
|
331
|
+
prompt: {
|
|
332
|
+
kind: "confirm",
|
|
333
|
+
message: "Confirm before closing a window with many tabs?",
|
|
334
|
+
initialValue: true
|
|
335
|
+
},
|
|
336
|
+
summarize: onOff
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "terminalIcons",
|
|
340
|
+
group: "Appearance",
|
|
341
|
+
label: "PowerShell file icons",
|
|
342
|
+
hint: "File icons in PowerShell",
|
|
343
|
+
prompt: {
|
|
344
|
+
kind: "confirm",
|
|
345
|
+
message: "Enable PowerShell file icons? This installs Terminal-Icons and Hack Nerd Font Mono if needed, adds it to your PowerShell profile, and switches Windows Terminal to that font.",
|
|
346
|
+
initialValue: true
|
|
347
|
+
},
|
|
348
|
+
summarize: onOff
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: "historyPreview",
|
|
352
|
+
group: "Features",
|
|
353
|
+
label: "Command history preview",
|
|
354
|
+
hint: "Browse recent commands",
|
|
355
|
+
prompt: {
|
|
356
|
+
kind: "confirm",
|
|
357
|
+
message: "Enable a scrollable PowerShell history list? This installs PSReadLine if needed and lets the Up and Down arrows browse the visible history suggestions.",
|
|
358
|
+
initialValue: true
|
|
359
|
+
},
|
|
360
|
+
summarize: onOff
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: "treeListing",
|
|
364
|
+
group: "Features",
|
|
365
|
+
label: "Tree-style ls",
|
|
366
|
+
hint: "Show files in a colored directory tree",
|
|
367
|
+
prompt: {
|
|
368
|
+
kind: "confirm",
|
|
369
|
+
message: "Show directory contents as a colored tree when you run ls? Output is capped at 50 entries by default. Use -Limit or -All for more. Get-ChildItem remains available as gci.",
|
|
370
|
+
initialValue: true
|
|
371
|
+
},
|
|
372
|
+
summarize: onOff
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: "hidePowerShellBanner",
|
|
376
|
+
group: "Appearance",
|
|
377
|
+
label: "Hide PowerShell startup banner",
|
|
378
|
+
hint: "Hide the welcome text",
|
|
379
|
+
prompt: {
|
|
380
|
+
kind: "confirm",
|
|
381
|
+
message: "Hide the Windows PowerShell startup banner?",
|
|
382
|
+
initialValue: true
|
|
383
|
+
},
|
|
384
|
+
summarize: onOff
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
id: "focusFollowMouse",
|
|
388
|
+
group: "Behavior",
|
|
389
|
+
label: "Focus follows mouse",
|
|
390
|
+
hint: "Focus panes on hover",
|
|
391
|
+
prompt: {
|
|
392
|
+
kind: "confirm",
|
|
393
|
+
message: "Focus panes when hovering with the mouse?",
|
|
394
|
+
initialValue: false
|
|
395
|
+
},
|
|
396
|
+
summarize: onOff
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "trimPaste",
|
|
400
|
+
group: "Behavior",
|
|
401
|
+
label: "Trim paste",
|
|
402
|
+
hint: "Remove trailing whitespace when pasting",
|
|
403
|
+
prompt: {
|
|
404
|
+
kind: "confirm",
|
|
405
|
+
message: "Trim trailing whitespace when pasting?",
|
|
406
|
+
initialValue: true
|
|
407
|
+
},
|
|
408
|
+
summarize: onOff
|
|
409
|
+
}
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
// src/fonts.ts
|
|
413
|
+
import { createHash } from "crypto";
|
|
414
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
415
|
+
import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
|
|
416
|
+
import { tmpdir } from "os";
|
|
417
|
+
import { join, resolve } from "path";
|
|
418
|
+
var googleFontsRevision = "29b4ec6df2522a8aee279414b78a07910b065874";
|
|
419
|
+
var hackRevision = "a737c121cabb337fdfe655d8c7304729f351e30f";
|
|
420
|
+
var fontRegistryKeys = [
|
|
421
|
+
"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts",
|
|
422
|
+
"HKCU\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
|
|
423
|
+
];
|
|
424
|
+
var userFontsRegistryKey = "HKCU\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
|
|
425
|
+
var NERD_FONT_FAMILY = "Hack Nerd Font Mono";
|
|
426
|
+
var nerdFontArchive = {
|
|
427
|
+
fileName: "Hack.tar.xz",
|
|
428
|
+
url: "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.5.1/Hack.tar.xz",
|
|
429
|
+
sha256: "cdd389472e10e2261520140ff1b382b4f8a226af5fd0b2735b975d31151d9c3c",
|
|
430
|
+
fontEntry: "HackNerdFontMono-Regular.ttf",
|
|
431
|
+
licenseEntry: "LICENSE.md"
|
|
432
|
+
};
|
|
433
|
+
var fontAssets = {
|
|
434
|
+
"Cascadia Mono": {
|
|
435
|
+
fileName: `pimpt-cascadia-mono-${googleFontsRevision.slice(0, 8)}.ttf`,
|
|
436
|
+
fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiamono/CascadiaMono%5Bwght%5D.ttf`,
|
|
437
|
+
licenseFileName: "pimpt-cascadia-mono-OFL.txt",
|
|
438
|
+
licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiamono/OFL.txt`
|
|
439
|
+
},
|
|
440
|
+
"Cascadia Code": {
|
|
441
|
+
fileName: `pimpt-cascadia-code-${googleFontsRevision.slice(0, 8)}.ttf`,
|
|
442
|
+
fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiacode/CascadiaCode%5Bwght%5D.ttf`,
|
|
443
|
+
licenseFileName: "pimpt-cascadia-code-OFL.txt",
|
|
444
|
+
licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/cascadiacode/OFL.txt`
|
|
445
|
+
},
|
|
446
|
+
"JetBrains Mono": {
|
|
447
|
+
fileName: `pimpt-jetbrains-mono-${googleFontsRevision.slice(0, 8)}.ttf`,
|
|
448
|
+
fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/jetbrainsmono/JetBrainsMono%5Bwght%5D.ttf`,
|
|
449
|
+
licenseFileName: "pimpt-jetbrains-mono-OFL.txt",
|
|
450
|
+
licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/jetbrainsmono/OFL.txt`
|
|
451
|
+
},
|
|
452
|
+
"Fira Code": {
|
|
453
|
+
fileName: `pimpt-fira-code-${googleFontsRevision.slice(0, 8)}.ttf`,
|
|
454
|
+
fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/firacode/FiraCode%5Bwght%5D.ttf`,
|
|
455
|
+
licenseFileName: "pimpt-fira-code-OFL.txt",
|
|
456
|
+
licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/firacode/OFL.txt`
|
|
457
|
+
},
|
|
458
|
+
Hack: {
|
|
459
|
+
fileName: `pimpt-hack-${hackRevision.slice(0, 8)}.ttf`,
|
|
460
|
+
fontUrl: `https://raw.githubusercontent.com/source-foundry/Hack/${hackRevision}/build/ttf/Hack-Regular.ttf`,
|
|
461
|
+
licenseFileName: "pimpt-hack-LICENSE.md",
|
|
462
|
+
licenseUrl: `https://raw.githubusercontent.com/source-foundry/Hack/${hackRevision}/LICENSE.md`
|
|
463
|
+
},
|
|
464
|
+
"Source Code Pro": {
|
|
465
|
+
fileName: `pimpt-source-code-pro-${googleFontsRevision.slice(0, 8)}.ttf`,
|
|
466
|
+
fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/sourcecodepro/SourceCodePro%5Bwght%5D.ttf`,
|
|
467
|
+
licenseFileName: "pimpt-source-code-pro-OFL.txt",
|
|
468
|
+
licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/sourcecodepro/OFL.txt`
|
|
469
|
+
},
|
|
470
|
+
"IBM Plex Mono": {
|
|
471
|
+
fileName: `pimpt-ibm-plex-mono-${googleFontsRevision.slice(0, 8)}.ttf`,
|
|
472
|
+
fontUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/ibmplexmono/IBMPlexMono-Regular.ttf`,
|
|
473
|
+
licenseFileName: "pimpt-ibm-plex-mono-OFL.txt",
|
|
474
|
+
licenseUrl: `https://raw.githubusercontent.com/google/fonts/${googleFontsRevision}/ofl/ibmplexmono/OFL.txt`
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
function assertWindows() {
|
|
478
|
+
if (process.platform !== "win32") {
|
|
479
|
+
throw new Error("Automatic font installation is only available on Windows.");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function queryFontRegistry(key) {
|
|
483
|
+
const result = spawnSync("reg.exe", ["query", key], {
|
|
484
|
+
encoding: "utf8",
|
|
485
|
+
windowsHide: true
|
|
486
|
+
});
|
|
487
|
+
if (result.error) {
|
|
488
|
+
throw new Error(`Could not query the Windows font registry: ${result.error.message}`, {
|
|
489
|
+
cause: result.error
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (result.status === 1) return "";
|
|
493
|
+
if (result.status !== 0) {
|
|
494
|
+
throw new Error(
|
|
495
|
+
`Could not query the Windows font registry: ${result.stderr.trim() || `reg.exe exited with code ${String(result.status)}`}`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
return result.stdout;
|
|
499
|
+
}
|
|
500
|
+
function isFontInstalled(fontFamily) {
|
|
501
|
+
assertWindows();
|
|
502
|
+
const family = fontFamily.toLocaleLowerCase("en-US");
|
|
503
|
+
return fontRegistryKeys.map(queryFontRegistry).join("\n").split(/\r?\n/).some((line) => line.trimStart().toLocaleLowerCase("en-US").startsWith(family));
|
|
504
|
+
}
|
|
505
|
+
function canInstallFont(fontFamily) {
|
|
506
|
+
return fontAssets[fontFamily] !== void 0;
|
|
507
|
+
}
|
|
508
|
+
async function download(url, description) {
|
|
509
|
+
let response;
|
|
510
|
+
try {
|
|
511
|
+
response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
512
|
+
} catch (error) {
|
|
513
|
+
throw new Error(`Could not download ${description}: ${String(error)}`, {
|
|
514
|
+
cause: error
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (!response.ok) {
|
|
518
|
+
throw new Error(`Could not download ${description}: HTTP ${response.status}.`);
|
|
519
|
+
}
|
|
520
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
521
|
+
if (data.length === 0 || data.length > 3e7) {
|
|
522
|
+
throw new Error(`Downloaded ${description} has an invalid file size.`);
|
|
523
|
+
}
|
|
524
|
+
return data;
|
|
525
|
+
}
|
|
526
|
+
function isFontFile(data) {
|
|
527
|
+
return data[0] === 0 && data[1] === 1 && data[2] === 0 && data[3] === 0 || data.toString("ascii", 0, 4) === "OTTO";
|
|
528
|
+
}
|
|
529
|
+
async function saveAndRegisterFont(localAppData, fontFamily, fileName, licenseFileName, fontData, licenseData) {
|
|
530
|
+
if (!isFontFile(fontData)) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
`The downloaded ${fontFamily} file is not a valid TrueType/OpenType font.`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
const licenseText = licenseData.toString("utf8").toLowerCase();
|
|
536
|
+
if (!licenseText.includes("license")) {
|
|
537
|
+
throw new Error(`The downloaded ${fontFamily} license file is invalid.`);
|
|
538
|
+
}
|
|
539
|
+
const fontDirectory = join(localAppData, "Microsoft", "Windows", "Fonts");
|
|
540
|
+
await mkdir(fontDirectory, { recursive: true });
|
|
541
|
+
const fontPath = join(fontDirectory, fileName);
|
|
542
|
+
const licensePath = join(fontDirectory, licenseFileName);
|
|
543
|
+
await writeFile(fontPath, fontData);
|
|
544
|
+
await writeFile(licensePath, licenseData);
|
|
545
|
+
execFileSync(
|
|
546
|
+
"reg.exe",
|
|
547
|
+
[
|
|
548
|
+
"add",
|
|
549
|
+
userFontsRegistryKey,
|
|
550
|
+
"/v",
|
|
551
|
+
`${fontFamily} (TrueType)`,
|
|
552
|
+
"/t",
|
|
553
|
+
"REG_SZ",
|
|
554
|
+
"/d",
|
|
555
|
+
fontPath,
|
|
556
|
+
"/f"
|
|
557
|
+
],
|
|
558
|
+
{ encoding: "utf8", windowsHide: true, stdio: "pipe" }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
async function installFont(fontFamily) {
|
|
562
|
+
assertWindows();
|
|
563
|
+
const asset = fontAssets[fontFamily];
|
|
564
|
+
if (!asset) {
|
|
565
|
+
throw new Error(`Automatic installation is not supported for ${fontFamily}.`);
|
|
566
|
+
}
|
|
567
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
568
|
+
if (!localAppData) {
|
|
569
|
+
throw new Error("LOCALAPPDATA is not set; the font cannot be installed.");
|
|
570
|
+
}
|
|
571
|
+
const [fontData, licenseData] = await Promise.all([
|
|
572
|
+
download(asset.fontUrl, `${fontFamily} font`),
|
|
573
|
+
download(asset.licenseUrl, `${fontFamily} license`)
|
|
574
|
+
]);
|
|
575
|
+
await saveAndRegisterFont(
|
|
576
|
+
localAppData,
|
|
577
|
+
fontFamily,
|
|
578
|
+
asset.fileName,
|
|
579
|
+
asset.licenseFileName,
|
|
580
|
+
fontData,
|
|
581
|
+
licenseData
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
async function installNerdFont() {
|
|
585
|
+
assertWindows();
|
|
586
|
+
if (isFontInstalled(NERD_FONT_FAMILY)) return;
|
|
587
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
588
|
+
if (!localAppData) {
|
|
589
|
+
throw new Error("LOCALAPPDATA is not set; the font cannot be installed.");
|
|
590
|
+
}
|
|
591
|
+
const archiveData = await download(nerdFontArchive.url, "Hack Nerd Font archive");
|
|
592
|
+
const actualHash = createHash("sha256").update(archiveData).digest("hex");
|
|
593
|
+
if (actualHash !== nerdFontArchive.sha256) {
|
|
594
|
+
throw new Error("The downloaded Hack Nerd Font archive failed its SHA-256 check.");
|
|
595
|
+
}
|
|
596
|
+
const temporaryDirectory = await mkdtemp(join(tmpdir(), "pimpt-nerd-font-"));
|
|
597
|
+
const archivePath = resolve(temporaryDirectory, nerdFontArchive.fileName);
|
|
598
|
+
try {
|
|
599
|
+
await writeFile(archivePath, archiveData);
|
|
600
|
+
let fontData;
|
|
601
|
+
let licenseData;
|
|
602
|
+
try {
|
|
603
|
+
fontData = execFileSync(
|
|
604
|
+
"tar.exe",
|
|
605
|
+
["-xOf", archivePath, nerdFontArchive.fontEntry],
|
|
606
|
+
{ maxBuffer: 1e7, windowsHide: true }
|
|
607
|
+
);
|
|
608
|
+
licenseData = execFileSync(
|
|
609
|
+
"tar.exe",
|
|
610
|
+
["-xOf", archivePath, nerdFontArchive.licenseEntry],
|
|
611
|
+
{ maxBuffer: 1e6, windowsHide: true }
|
|
612
|
+
);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
throw new Error("Could not extract Hack Nerd Font from its archive.", {
|
|
615
|
+
cause: error
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
await saveAndRegisterFont(
|
|
619
|
+
localAppData,
|
|
620
|
+
NERD_FONT_FAMILY,
|
|
621
|
+
"pimpt-hack-nerd-font-v3.5.1.ttf",
|
|
622
|
+
"pimpt-hack-nerd-font-LICENSE.md",
|
|
623
|
+
fontData,
|
|
624
|
+
licenseData
|
|
625
|
+
);
|
|
626
|
+
} finally {
|
|
627
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/powershell.ts
|
|
632
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
633
|
+
var terminalIconsVersion = "0.11.0";
|
|
634
|
+
var psReadLineVersion = "2.4.5";
|
|
635
|
+
function runPowerShell(script, operation) {
|
|
636
|
+
if (process.platform !== "win32") {
|
|
637
|
+
throw new Error(`${operation} is only supported on Windows.`);
|
|
638
|
+
}
|
|
639
|
+
try {
|
|
640
|
+
execFileSync2(
|
|
641
|
+
"powershell.exe",
|
|
642
|
+
[
|
|
643
|
+
"-NoLogo",
|
|
644
|
+
"-NoProfile",
|
|
645
|
+
"-NonInteractive",
|
|
646
|
+
"-Command",
|
|
647
|
+
script
|
|
648
|
+
],
|
|
649
|
+
{ encoding: "utf8", windowsHide: true, stdio: "pipe" }
|
|
650
|
+
);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
throw new Error(`${operation} failed: ${String(error)}`, { cause: error });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function installPowerShellGalleryModule(moduleName, version, allowClobber) {
|
|
656
|
+
const clobberOption = allowClobber ? "-AllowClobber " : "";
|
|
657
|
+
const script = String.raw`$ErrorActionPreference = 'Stop'
|
|
658
|
+
$protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
|
659
|
+
[Net.ServicePointManager]::SecurityProtocol = $protocols
|
|
660
|
+
$nugetProvider = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
|
|
661
|
+
if (-not $nugetProvider) {
|
|
662
|
+
Install-PackageProvider -Name NuGet -MinimumVersion '2.8.5.201' -Scope CurrentUser -Force -Confirm:$false -ErrorAction Stop | Out-Null
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
$requiredVersion = [version]'${version}'
|
|
666
|
+
$module = Get-Module -ListAvailable -Name '${moduleName}' | Where-Object { $_.Version -eq $requiredVersion }
|
|
667
|
+
if (-not $module) {
|
|
668
|
+
Install-Module -Name '${moduleName}' -RequiredVersion '${version}' -Scope CurrentUser -Repository PSGallery ${clobberOption}-Force -Confirm:$false -ErrorAction Stop
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
$module = Get-Module -ListAvailable -Name '${moduleName}' | Where-Object { $_.Version -eq $requiredVersion }
|
|
672
|
+
if (-not $module) {
|
|
673
|
+
throw '${moduleName} ${version} is not available after installation.'
|
|
674
|
+
}`;
|
|
675
|
+
runPowerShell(script, `Installing the ${moduleName} PowerShell module`);
|
|
676
|
+
}
|
|
677
|
+
function addPowerShellProfileBlock(feature, commands, operation) {
|
|
678
|
+
const beginMarker = `# >>> pimpt ${feature} >>>`;
|
|
679
|
+
const endMarker = `# <<< pimpt ${feature} <<<`;
|
|
680
|
+
const block = [beginMarker, ...commands, endMarker].join("\r\n");
|
|
681
|
+
const encodedMarker = Buffer.from(beginMarker, "utf16le").toString("base64");
|
|
682
|
+
const encodedBlock = Buffer.from(block, "utf16le").toString("base64");
|
|
683
|
+
const script = String.raw`$ErrorActionPreference = 'Stop'
|
|
684
|
+
$profilePath = $PROFILE.CurrentUserCurrentHost
|
|
685
|
+
$profileDirectory = Split-Path -Parent $profilePath
|
|
686
|
+
if (-not (Test-Path -LiteralPath $profileDirectory)) {
|
|
687
|
+
New-Item -ItemType Directory -Path $profileDirectory -Force | Out-Null
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
$profileContent = ''
|
|
691
|
+
if (Test-Path -LiteralPath $profilePath) {
|
|
692
|
+
$profileContent = Get-Content -LiteralPath $profilePath -Raw
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
$beginMarker = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('${encodedMarker}'))
|
|
696
|
+
if ($profileContent -notmatch [regex]::Escape($beginMarker)) {
|
|
697
|
+
if (-not (Test-Path -LiteralPath $profilePath)) {
|
|
698
|
+
New-Item -ItemType File -Path $profilePath -Force | Out-Null
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
$encoding = 'Default'
|
|
702
|
+
$bytes = [System.IO.File]::ReadAllBytes($profilePath)
|
|
703
|
+
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
|
|
704
|
+
$encoding = 'Unicode'
|
|
705
|
+
} elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
|
|
706
|
+
$encoding = 'BigEndianUnicode'
|
|
707
|
+
} elseif ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
|
|
708
|
+
$encoding = 'UTF8'
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
$profileBlock = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('${encodedBlock}'))
|
|
712
|
+
$newline = [Environment]::NewLine
|
|
713
|
+
$separator = if ($profileContent.Length -gt 0 -and -not $profileContent.EndsWith([string][char]10)) { $newline } else { '' }
|
|
714
|
+
Add-Content -LiteralPath $profilePath -Encoding $encoding -Value "$separator$profileBlock"
|
|
715
|
+
}`;
|
|
716
|
+
runPowerShell(script, operation);
|
|
717
|
+
}
|
|
718
|
+
function installTerminalIconsModule() {
|
|
719
|
+
installPowerShellGalleryModule(
|
|
720
|
+
"Terminal-Icons",
|
|
721
|
+
terminalIconsVersion,
|
|
722
|
+
false
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
function installPSReadLineModule() {
|
|
726
|
+
installPowerShellGalleryModule("PSReadLine", psReadLineVersion, true);
|
|
727
|
+
}
|
|
728
|
+
function addTerminalIconsToPowerShellProfile() {
|
|
729
|
+
addPowerShellProfileBlock(
|
|
730
|
+
"terminal icons",
|
|
731
|
+
["Import-Module Terminal-Icons -ErrorAction Stop"],
|
|
732
|
+
"Adding Terminal-Icons to the Windows PowerShell profile"
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
function addHistoryPreviewToPowerShellProfile() {
|
|
736
|
+
addPowerShellProfileBlock(
|
|
737
|
+
"command history preview",
|
|
738
|
+
[
|
|
739
|
+
`Import-Module PSReadLine -RequiredVersion '${psReadLineVersion}' -Force -ErrorAction Stop`,
|
|
740
|
+
"Set-PSReadLineOption -PredictionSource History -PredictionViewStyle ListView"
|
|
741
|
+
],
|
|
742
|
+
"Adding command history preview to the Windows PowerShell profile"
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
function readPowerShellProfileFeatures() {
|
|
746
|
+
const features = {
|
|
747
|
+
terminalIcons: false,
|
|
748
|
+
historyPreview: false,
|
|
749
|
+
treeListing: false
|
|
750
|
+
};
|
|
751
|
+
if (process.platform !== "win32") return features;
|
|
752
|
+
let encodedProfile;
|
|
753
|
+
try {
|
|
754
|
+
encodedProfile = execFileSync2(
|
|
755
|
+
"powershell.exe",
|
|
756
|
+
[
|
|
757
|
+
"-NoLogo",
|
|
758
|
+
"-NoProfile",
|
|
759
|
+
"-NonInteractive",
|
|
760
|
+
"-Command",
|
|
761
|
+
String.raw`$profilePath = $PROFILE.CurrentUserCurrentHost
|
|
762
|
+
if (Test-Path -LiteralPath $profilePath) {
|
|
763
|
+
$content = [System.IO.File]::ReadAllText($profilePath)
|
|
764
|
+
[Console]::Out.Write([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($content)))
|
|
765
|
+
}`
|
|
766
|
+
],
|
|
767
|
+
{ encoding: "utf8", windowsHide: true, stdio: "pipe" }
|
|
768
|
+
).trim();
|
|
769
|
+
} catch (error) {
|
|
770
|
+
throw new Error("Could not inspect the Windows PowerShell profile.", {
|
|
771
|
+
cause: error
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
const profile = Buffer.from(encodedProfile, "base64").toString("utf16le");
|
|
775
|
+
const hasBlock = (name) => profile.includes(`# >>> pimpt ${name} >>>`) && profile.includes(`# <<< pimpt ${name} <<<`);
|
|
776
|
+
features.terminalIcons = hasBlock("terminal icons");
|
|
777
|
+
features.historyPreview = hasBlock("command history preview");
|
|
778
|
+
features.treeListing = hasBlock("tree-style ls v2") || hasBlock("tree-style ls");
|
|
779
|
+
return features;
|
|
780
|
+
}
|
|
781
|
+
function addTreeListingToPowerShellProfile() {
|
|
782
|
+
addPowerShellProfileBlock(
|
|
783
|
+
"tree-style ls alias",
|
|
784
|
+
["Remove-Item Alias:ls -Force -ErrorAction SilentlyContinue"],
|
|
785
|
+
"Enabling the tree-style ls command in the Windows PowerShell profile"
|
|
786
|
+
);
|
|
787
|
+
addPowerShellProfileBlock(
|
|
788
|
+
"tree-style ls v2",
|
|
789
|
+
[
|
|
790
|
+
"function ls {",
|
|
791
|
+
" [CmdletBinding()]",
|
|
792
|
+
" param(",
|
|
793
|
+
" [Parameter(Position = 0)] [string]$Path = '.',",
|
|
794
|
+
" [ValidateRange(1, 32)] [int]$Depth = 4,",
|
|
795
|
+
" [ValidateRange(1, 10000)] [int]$Limit = 50,",
|
|
796
|
+
" [switch]$All,",
|
|
797
|
+
" [switch]$Force",
|
|
798
|
+
" )",
|
|
799
|
+
" try {",
|
|
800
|
+
" $root = Get-Item -LiteralPath $Path -ErrorAction Stop",
|
|
801
|
+
" } catch {",
|
|
802
|
+
` Write-Host ("ls: cannot access '{0}': {1}" -f $Path, $_.Exception.Message) -ForegroundColor Red`,
|
|
803
|
+
" return",
|
|
804
|
+
" }",
|
|
805
|
+
" if (-not $root.PSIsContainer) {",
|
|
806
|
+
" Write-Host $root.Name -ForegroundColor Gray",
|
|
807
|
+
" return",
|
|
808
|
+
" }",
|
|
809
|
+
" Write-Host $root.FullName -ForegroundColor Cyan",
|
|
810
|
+
" $state = [pscustomobject]@{ Shown = 0; Limit = $Limit; All = [bool]$All; Truncated = $false }",
|
|
811
|
+
" $walk = {",
|
|
812
|
+
" param($Directory, [string]$Prefix, [int]$CurrentDepth, $State)",
|
|
813
|
+
" if ($CurrentDepth -ge $Depth) { return }",
|
|
814
|
+
" try {",
|
|
815
|
+
" $children = @(Get-ChildItem -LiteralPath $Directory.FullName -Force:$Force -ErrorAction Stop | Sort-Object @{ Expression = { if ($_.PSIsContainer) { 0 } else { 1 } } }, Name)",
|
|
816
|
+
" } catch {",
|
|
817
|
+
' Write-Host ("{0}\u2514\u2500\u2500 [cannot read: {1}]" -f $Prefix, $_.Exception.Message) -ForegroundColor Red',
|
|
818
|
+
" return",
|
|
819
|
+
" }",
|
|
820
|
+
" for ($index = 0; $index -lt $children.Count; $index++) {",
|
|
821
|
+
" if (-not $State.All -and $State.Shown -ge $State.Limit) {",
|
|
822
|
+
" $State.Truncated = $true",
|
|
823
|
+
" break",
|
|
824
|
+
" }",
|
|
825
|
+
" $entry = $children[$index]",
|
|
826
|
+
" $isLast = $index -eq ($children.Count - 1)",
|
|
827
|
+
" $connector = if ($isLast) { '\u2514\u2500\u2500 ' } else { '\u251C\u2500\u2500 ' }",
|
|
828
|
+
" $color = if ($entry.PSIsContainer) { 'Cyan' } else { 'Gray' }",
|
|
829
|
+
" Write-Host ($Prefix + $connector + $entry.Name) -ForegroundColor $color",
|
|
830
|
+
" $State.Shown++",
|
|
831
|
+
" if ($entry.PSIsContainer -and ($CurrentDepth + 1 -lt $Depth)) {",
|
|
832
|
+
" $childPrefix = $Prefix + $(if ($isLast) { ' ' } else { '\u2502 ' })",
|
|
833
|
+
" & $walk $entry $childPrefix ($CurrentDepth + 1) $State",
|
|
834
|
+
" if ($State.Truncated) { break }",
|
|
835
|
+
" }",
|
|
836
|
+
" }",
|
|
837
|
+
" }",
|
|
838
|
+
" & $walk $root '' 0 $state",
|
|
839
|
+
" if ($state.Truncated) {",
|
|
840
|
+
' Write-Host ("\u2026 output capped at {0} entries (use -Limit <number> or -All)" -f $state.Limit) -ForegroundColor DarkGray',
|
|
841
|
+
" }",
|
|
842
|
+
"}"
|
|
843
|
+
],
|
|
844
|
+
"Adding the tree-style ls command to the Windows PowerShell profile"
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/summary.ts
|
|
849
|
+
function normalizeHex(value) {
|
|
850
|
+
return value.trim().replace(/^#?/, "#").toUpperCase();
|
|
851
|
+
}
|
|
852
|
+
function buildSummarySections(answers) {
|
|
853
|
+
const sections = [];
|
|
854
|
+
let current = null;
|
|
855
|
+
for (const setting of settings) {
|
|
856
|
+
const value = answers[setting.id];
|
|
857
|
+
if (value === void 0) continue;
|
|
858
|
+
if (!current || current.group !== setting.group) {
|
|
859
|
+
current = { group: setting.group, rows: [] };
|
|
860
|
+
sections.push(current);
|
|
861
|
+
}
|
|
862
|
+
current.rows.push([setting.label, setting.summarize(value)]);
|
|
863
|
+
}
|
|
864
|
+
return sections;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/wtsettings.ts
|
|
868
|
+
import {
|
|
869
|
+
copyFileSync,
|
|
870
|
+
existsSync,
|
|
871
|
+
lstatSync,
|
|
872
|
+
readFileSync,
|
|
873
|
+
readdirSync,
|
|
874
|
+
writeFileSync
|
|
875
|
+
} from "fs";
|
|
876
|
+
import { basename, dirname, join as join2 } from "path";
|
|
877
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
878
|
+
var InvalidSettingsFileError = class extends Error {
|
|
879
|
+
constructor() {
|
|
880
|
+
super(
|
|
881
|
+
"Windows Terminal settings.json is invalid JSONC. No changes were written. Restore the file from a backup before trying again."
|
|
882
|
+
);
|
|
883
|
+
this.name = "InvalidSettingsFileError";
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
function findSettingsPath() {
|
|
887
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
888
|
+
if (!localAppData) return void 0;
|
|
889
|
+
const candidates = [
|
|
890
|
+
join2(localAppData, "Packages", "Microsoft.WindowsTerminal_8wekyb3d8bbwe", "LocalState", "settings.json"),
|
|
891
|
+
join2(localAppData, "Packages", "Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe", "LocalState", "settings.json"),
|
|
892
|
+
join2(localAppData, "Packages", "Microsoft.WindowsTerminal.Canary_8wekyb3d8bbwe", "LocalState", "settings.json")
|
|
893
|
+
];
|
|
894
|
+
for (const path of candidates) {
|
|
895
|
+
if (existsSync(path)) return path;
|
|
896
|
+
}
|
|
897
|
+
return void 0;
|
|
898
|
+
}
|
|
899
|
+
function readSettingsFile(settingsPath) {
|
|
900
|
+
return readFileSync(settingsPath, "utf8");
|
|
901
|
+
}
|
|
902
|
+
function writeSettingsFile(settingsPath, content) {
|
|
903
|
+
writeFileSync(settingsPath, content, "utf8");
|
|
904
|
+
}
|
|
905
|
+
function createSettingsBackup(settingsPath) {
|
|
906
|
+
const timestamp = Date.now();
|
|
907
|
+
const backupPrefix = `${basename(settingsPath)}.backup-${timestamp}-`;
|
|
908
|
+
let sequence = 0;
|
|
909
|
+
let backupPath;
|
|
910
|
+
do {
|
|
911
|
+
backupPath = join2(dirname(settingsPath), `${backupPrefix}${sequence}`);
|
|
912
|
+
sequence += 1;
|
|
913
|
+
} while (existsSync(backupPath));
|
|
914
|
+
copyFileSync(settingsPath, backupPath);
|
|
915
|
+
return backupPath;
|
|
916
|
+
}
|
|
917
|
+
function listSettingsBackups(settingsPath) {
|
|
918
|
+
const backupPrefix = `${basename(settingsPath)}.backup-`;
|
|
919
|
+
return readdirSync(dirname(settingsPath)).filter((name) => name.startsWith(backupPrefix)).flatMap((name) => {
|
|
920
|
+
const match = /^(\d+)-(\d+)$/.exec(name.slice(backupPrefix.length));
|
|
921
|
+
if (!match) return [];
|
|
922
|
+
const path = join2(dirname(settingsPath), name);
|
|
923
|
+
if (!lstatSync(path).isFile()) return [];
|
|
924
|
+
return [{ path, createdAt: new Date(Number(match[1])) }];
|
|
925
|
+
}).sort((left, right) => right.createdAt.getTime() - left.createdAt.getTime());
|
|
926
|
+
}
|
|
927
|
+
function restoreSettingsBackup(settingsPath, backupPath) {
|
|
928
|
+
const backup = listSettingsBackups(settingsPath).find(
|
|
929
|
+
(candidate) => candidate.path === backupPath
|
|
930
|
+
);
|
|
931
|
+
if (!backup) {
|
|
932
|
+
throw new Error("The selected settings backup is not available.");
|
|
933
|
+
}
|
|
934
|
+
const content = readFileSync(backup.path, "utf8");
|
|
935
|
+
const parseErrors = [];
|
|
936
|
+
const parsedContent = parse(content, parseErrors, { allowTrailingComma: true });
|
|
937
|
+
if (parseErrors.length > 0 || parsedContent === null || typeof parsedContent !== "object" || Array.isArray(parsedContent)) {
|
|
938
|
+
throw new Error("The selected backup is invalid JSONC. Nothing was changed.");
|
|
939
|
+
}
|
|
940
|
+
const safetyBackupPath = createSettingsBackup(settingsPath);
|
|
941
|
+
writeSettingsFile(settingsPath, content);
|
|
942
|
+
return safetyBackupPath;
|
|
943
|
+
}
|
|
944
|
+
function setSetting(content, jsonPath, value) {
|
|
945
|
+
const parseErrors = [];
|
|
946
|
+
const parsedContent = parse(content, parseErrors, { allowTrailingComma: true });
|
|
947
|
+
if (parseErrors.length > 0 || parsedContent === null || typeof parsedContent !== "object" || Array.isArray(parsedContent)) {
|
|
948
|
+
throw new InvalidSettingsFileError();
|
|
949
|
+
}
|
|
950
|
+
const edits = modify(content, jsonPath, value, {
|
|
951
|
+
formattingOptions: {
|
|
952
|
+
tabSize: 4,
|
|
953
|
+
insertSpaces: true
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
return applyEdits(content, edits);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/installer.ts
|
|
960
|
+
var BANNER = `
|
|
961
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
962
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
|
|
963
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551
|
|
964
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551
|
|
965
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
966
|
+
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
967
|
+
`;
|
|
968
|
+
function bail(value) {
|
|
969
|
+
if (isCancel(value)) {
|
|
970
|
+
cancel("Setup cancelled. Nothing was changed.");
|
|
971
|
+
process.exit(0);
|
|
972
|
+
}
|
|
973
|
+
return value;
|
|
974
|
+
}
|
|
975
|
+
var StartingDirectoryInputError = class extends Error {
|
|
976
|
+
};
|
|
977
|
+
function normalizeStartingDirectory(value) {
|
|
978
|
+
let directory = value.trim();
|
|
979
|
+
if (!directory) return "";
|
|
980
|
+
if (directory.startsWith('"') && directory.endsWith('"') || directory.startsWith("'") && directory.endsWith("'")) {
|
|
981
|
+
directory = directory.slice(1, -1).trim();
|
|
982
|
+
}
|
|
983
|
+
if (!directory) return "";
|
|
984
|
+
const userProfile = process.env.USERPROFILE ?? process.env.HOME;
|
|
985
|
+
if (/^~(?=$|[\\/])/.test(directory)) {
|
|
986
|
+
if (!userProfile) {
|
|
987
|
+
throw new StartingDirectoryInputError(
|
|
988
|
+
"Cannot resolve '~' because the user folder is unknown."
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
directory = directory.replace(/^~/, userProfile);
|
|
992
|
+
}
|
|
993
|
+
directory = directory.replace(/%([^%]+)%/g, (variable, name) => {
|
|
994
|
+
const key = Object.keys(process.env).find(
|
|
995
|
+
(candidate) => candidate.toLowerCase() === name.toLowerCase()
|
|
996
|
+
);
|
|
997
|
+
const environmentValue = key ? process.env[key] : void 0;
|
|
998
|
+
if (environmentValue === void 0) {
|
|
999
|
+
throw new StartingDirectoryInputError(
|
|
1000
|
+
`Unknown environment variable in starting path: ${variable}`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
return environmentValue;
|
|
1004
|
+
});
|
|
1005
|
+
if (/^[a-zA-Z]:(?![\\/])/.test(directory)) {
|
|
1006
|
+
throw new StartingDirectoryInputError(
|
|
1007
|
+
"Use an absolute drive path, for example C:\\Users\\you\\Downloads."
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
if (/^[\\/](?![\\/])/.test(directory)) {
|
|
1011
|
+
if (!userProfile) {
|
|
1012
|
+
throw new StartingDirectoryInputError(
|
|
1013
|
+
"Cannot resolve a rooted path because the user folder is unknown."
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
directory = pathWin32.resolve(pathWin32.parse(userProfile).root, directory);
|
|
1017
|
+
}
|
|
1018
|
+
if (pathWin32.isAbsolute(directory)) return pathWin32.normalize(directory);
|
|
1019
|
+
if (!userProfile) {
|
|
1020
|
+
throw new StartingDirectoryInputError(
|
|
1021
|
+
"Cannot resolve a relative starting path because the user folder is unknown."
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
return pathWin32.resolve(userProfile, directory);
|
|
1025
|
+
}
|
|
1026
|
+
function validateStartingDirectory(value) {
|
|
1027
|
+
let directory;
|
|
1028
|
+
try {
|
|
1029
|
+
directory = normalizeStartingDirectory(value ?? "");
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
if (error instanceof StartingDirectoryInputError) return error.message;
|
|
1032
|
+
throw error;
|
|
1033
|
+
}
|
|
1034
|
+
if (!directory || process.platform !== "win32") return void 0;
|
|
1035
|
+
try {
|
|
1036
|
+
if (!statSync(directory).isDirectory()) {
|
|
1037
|
+
return "The starting path must be an existing folder.";
|
|
1038
|
+
}
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
if (error !== null && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
|
1041
|
+
return `Folder not found: ${directory}`;
|
|
1042
|
+
}
|
|
1043
|
+
if (error !== null && typeof error === "object" && "code" in error && (error.code === "EACCES" || error.code === "EPERM")) {
|
|
1044
|
+
return `Cannot access folder: ${directory}`;
|
|
1045
|
+
}
|
|
1046
|
+
throw error;
|
|
1047
|
+
}
|
|
1048
|
+
return void 0;
|
|
1049
|
+
}
|
|
1050
|
+
async function askSetting(setting, currentValue) {
|
|
1051
|
+
const prompt = setting.prompt;
|
|
1052
|
+
if (prompt.kind === "select") {
|
|
1053
|
+
const selectValue = async () => bail(
|
|
1054
|
+
await select({
|
|
1055
|
+
message: prompt.message,
|
|
1056
|
+
options: prompt.options,
|
|
1057
|
+
initialValue: prompt.initialValue
|
|
1058
|
+
})
|
|
1059
|
+
);
|
|
1060
|
+
if (setting.id === "fontFace") {
|
|
1061
|
+
while (true) {
|
|
1062
|
+
const fontFamily = await selectValue();
|
|
1063
|
+
if (fontFamily === "keep" || isFontInstalled(fontFamily)) {
|
|
1064
|
+
return fontFamily;
|
|
1065
|
+
}
|
|
1066
|
+
if (!canInstallFont(fontFamily)) {
|
|
1067
|
+
log.warn(
|
|
1068
|
+
pc.yellow(
|
|
1069
|
+
`${fontFamily} is not installed and cannot be installed automatically. Choose another font.`
|
|
1070
|
+
)
|
|
1071
|
+
);
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
const shouldInstall = bail(
|
|
1075
|
+
await confirm({
|
|
1076
|
+
message: `${fontFamily} is not installed. Install it now? Select No to choose another font.`,
|
|
1077
|
+
initialValue: true
|
|
1078
|
+
})
|
|
1079
|
+
);
|
|
1080
|
+
if (!shouldInstall) continue;
|
|
1081
|
+
const fontSpinner = spinner();
|
|
1082
|
+
fontSpinner.start(`Installing ${fontFamily}\u2026`);
|
|
1083
|
+
try {
|
|
1084
|
+
await installFont(fontFamily);
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
fontSpinner.error(`Could not install ${fontFamily}.`);
|
|
1087
|
+
throw error;
|
|
1088
|
+
}
|
|
1089
|
+
fontSpinner.stop(
|
|
1090
|
+
`${fontFamily} installed. Restart Windows Terminal to use it.`
|
|
1091
|
+
);
|
|
1092
|
+
return fontFamily;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
const value = await selectValue();
|
|
1096
|
+
if (setting.id === "background" && value === BACKGROUND_CUSTOM) {
|
|
1097
|
+
const custom = bail(
|
|
1098
|
+
await text({
|
|
1099
|
+
message: "Enter a background color",
|
|
1100
|
+
placeholder: "#1E1E2E",
|
|
1101
|
+
initialValue: "",
|
|
1102
|
+
validate: (input) => {
|
|
1103
|
+
const raw = input ?? "";
|
|
1104
|
+
return /^#?[0-9a-fA-F]{6}$/.test(raw.trim()) ? void 0 : "Use a 6-digit hex color, e.g. #1E1E2E";
|
|
1105
|
+
}
|
|
1106
|
+
})
|
|
1107
|
+
);
|
|
1108
|
+
return normalizeHex(custom);
|
|
1109
|
+
}
|
|
1110
|
+
return value;
|
|
1111
|
+
}
|
|
1112
|
+
if (prompt.kind === "text") {
|
|
1113
|
+
const value = bail(
|
|
1114
|
+
await text({
|
|
1115
|
+
message: prompt.message,
|
|
1116
|
+
placeholder: prompt.placeholder,
|
|
1117
|
+
initialValue: currentValue === void 0 ? prompt.initialValue : String(currentValue),
|
|
1118
|
+
...setting.id === "startingDirectory" ? { validate: validateStartingDirectory } : prompt.validate ? { validate: prompt.validate } : {}
|
|
1119
|
+
})
|
|
1120
|
+
);
|
|
1121
|
+
return setting.id === "startingDirectory" ? normalizeStartingDirectory(value) : value;
|
|
1122
|
+
}
|
|
1123
|
+
return bail(
|
|
1124
|
+
await confirm({
|
|
1125
|
+
message: prompt.message,
|
|
1126
|
+
initialValue: prompt.initialValue
|
|
1127
|
+
})
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
function renderSummary(answers) {
|
|
1131
|
+
const sections = buildSummarySections(answers);
|
|
1132
|
+
if (sections.length === 0) {
|
|
1133
|
+
note("No settings selected.", "Configuration summary");
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
const labelWidth = Math.max(
|
|
1137
|
+
...sections.flatMap(
|
|
1138
|
+
(section) => section.rows.map(([label]) => label.length)
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1141
|
+
const lines = [];
|
|
1142
|
+
sections.forEach((section, index) => {
|
|
1143
|
+
if (index > 0) lines.push("");
|
|
1144
|
+
lines.push(pc.bold(pc.cyan(section.group)));
|
|
1145
|
+
for (const [label, value] of section.rows) {
|
|
1146
|
+
lines.push(` ${pc.dim(label.padEnd(labelWidth))} ${value}`);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
note(lines.join("\n"), "Your configuration");
|
|
1150
|
+
}
|
|
1151
|
+
function openNewTerminalTab() {
|
|
1152
|
+
if (!process.env.WT_SESSION) return false;
|
|
1153
|
+
try {
|
|
1154
|
+
const terminal = spawn("wt.exe", ["-w", "0", "new-tab"], {
|
|
1155
|
+
detached: true,
|
|
1156
|
+
stdio: "ignore",
|
|
1157
|
+
windowsHide: true
|
|
1158
|
+
});
|
|
1159
|
+
terminal.on("error", (error) => {
|
|
1160
|
+
log.warn(
|
|
1161
|
+
pc.yellow(`Could not open a new Windows Terminal tab: ${error.message}`)
|
|
1162
|
+
);
|
|
1163
|
+
});
|
|
1164
|
+
terminal.unref();
|
|
1165
|
+
return true;
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
log.warn(
|
|
1168
|
+
pc.yellow(`Could not open a new Windows Terminal tab: ${String(error)}`)
|
|
1169
|
+
);
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function getDefaultProfileIndex(content) {
|
|
1174
|
+
const parsed = parse2(content);
|
|
1175
|
+
if (typeof parsed.defaultProfile !== "string" || !parsed.profiles || typeof parsed.profiles !== "object" || !Array.isArray(parsed.profiles.list)) {
|
|
1176
|
+
return void 0;
|
|
1177
|
+
}
|
|
1178
|
+
const index = parsed.profiles.list.findIndex(
|
|
1179
|
+
(profile) => profile !== null && typeof profile === "object" && "guid" in profile && profile.guid === parsed.defaultProfile
|
|
1180
|
+
);
|
|
1181
|
+
return index < 0 ? void 0 : index;
|
|
1182
|
+
}
|
|
1183
|
+
function getWindowsPowerShellProfiles(content) {
|
|
1184
|
+
const parseErrors = [];
|
|
1185
|
+
const parsed = parse2(content, parseErrors, {
|
|
1186
|
+
allowTrailingComma: true
|
|
1187
|
+
});
|
|
1188
|
+
if (parseErrors.length > 0 || parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1189
|
+
throw new InvalidSettingsFileError();
|
|
1190
|
+
}
|
|
1191
|
+
const profiles = "profiles" in parsed ? parsed.profiles : void 0;
|
|
1192
|
+
if (profiles === null || typeof profiles !== "object" || Array.isArray(profiles) || !("list" in profiles) || !Array.isArray(profiles.list)) {
|
|
1193
|
+
return [];
|
|
1194
|
+
}
|
|
1195
|
+
return profiles.list.flatMap((profile, index) => {
|
|
1196
|
+
if (profile === null || typeof profile !== "object" || Array.isArray(profile)) {
|
|
1197
|
+
return [];
|
|
1198
|
+
}
|
|
1199
|
+
const name = "name" in profile && typeof profile.name === "string" ? profile.name : "";
|
|
1200
|
+
const commandline = "commandline" in profile && typeof profile.commandline === "string" ? profile.commandline : void 0;
|
|
1201
|
+
return name.toLowerCase() === "windows powershell" || /(?:^|[\\/"])powershell\.exe(?:["\s]|$)/i.test(commandline ?? "") ? [
|
|
1202
|
+
{
|
|
1203
|
+
index,
|
|
1204
|
+
...commandline !== void 0 ? { commandline } : {}
|
|
1205
|
+
}
|
|
1206
|
+
] : [];
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
function readConfiguredAnswers(settingsPath) {
|
|
1210
|
+
const answers = {};
|
|
1211
|
+
try {
|
|
1212
|
+
const profileFeatures = readPowerShellProfileFeatures();
|
|
1213
|
+
if (profileFeatures.terminalIcons) answers.terminalIcons = true;
|
|
1214
|
+
if (profileFeatures.historyPreview) answers.historyPreview = true;
|
|
1215
|
+
if (profileFeatures.treeListing) answers.treeListing = true;
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
log.warn(
|
|
1218
|
+
pc.yellow(
|
|
1219
|
+
`Could not detect configured PowerShell features: ${error instanceof Error ? error.message : String(error)}`
|
|
1220
|
+
)
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
if (!settingsPath) return answers;
|
|
1224
|
+
const content = readSettingsFile(settingsPath);
|
|
1225
|
+
const parseErrors = [];
|
|
1226
|
+
const parsed = parse2(content, parseErrors, {
|
|
1227
|
+
allowTrailingComma: true
|
|
1228
|
+
});
|
|
1229
|
+
if (parseErrors.length > 0 || parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1230
|
+
log.warn(
|
|
1231
|
+
pc.yellow(
|
|
1232
|
+
"Could not read existing settings.json values because the file contains invalid JSONC."
|
|
1233
|
+
)
|
|
1234
|
+
);
|
|
1235
|
+
return answers;
|
|
1236
|
+
}
|
|
1237
|
+
const profiles = "profiles" in parsed && parsed.profiles !== null ? parsed.profiles : void 0;
|
|
1238
|
+
const profileList = profiles !== null && typeof profiles === "object" && !Array.isArray(profiles) && "list" in profiles && Array.isArray(profiles.list) ? profiles.list : [];
|
|
1239
|
+
const defaultProfileGuid = "defaultProfile" in parsed ? parsed.defaultProfile : void 0;
|
|
1240
|
+
const defaultProfile = typeof defaultProfileGuid === "string" ? profileList.find(
|
|
1241
|
+
(profile) => profile !== null && typeof profile === "object" && !Array.isArray(profile) && "guid" in profile && profile.guid === defaultProfileGuid
|
|
1242
|
+
) : void 0;
|
|
1243
|
+
const defaults = profiles !== null && typeof profiles === "object" && !Array.isArray(profiles) && "defaults" in profiles ? profiles.defaults : void 0;
|
|
1244
|
+
if (defaults !== null && typeof defaults === "object" && !Array.isArray(defaults)) {
|
|
1245
|
+
if ("opacity" in defaults && typeof defaults.opacity === "number" && Number.isInteger(defaults.opacity) && defaults.opacity >= 0 && defaults.opacity <= 100) {
|
|
1246
|
+
answers.transparency = defaults.opacity === 100 && "useAcrylic" in defaults && defaults.useAcrylic === false ? "off" : String(defaults.opacity);
|
|
1247
|
+
}
|
|
1248
|
+
if ("background" in defaults && typeof defaults.background === "string") {
|
|
1249
|
+
answers.background = defaults.background;
|
|
1250
|
+
}
|
|
1251
|
+
const defaultStartingDirectory = "startingDirectory" in defaults && typeof defaults.startingDirectory === "string" ? defaults.startingDirectory : void 0;
|
|
1252
|
+
const font = "font" in defaults && defaults.font !== null ? defaults.font : void 0;
|
|
1253
|
+
if (font !== null && typeof font === "object" && !Array.isArray(font) && "face" in font && typeof font.face === "string") {
|
|
1254
|
+
answers.fontFace = font.face;
|
|
1255
|
+
}
|
|
1256
|
+
const profileStartingDirectory = defaultProfile !== null && typeof defaultProfile === "object" && !Array.isArray(defaultProfile) && "startingDirectory" in defaultProfile && typeof defaultProfile.startingDirectory === "string" ? defaultProfile.startingDirectory : void 0;
|
|
1257
|
+
const startingDirectory = profileStartingDirectory ?? defaultStartingDirectory;
|
|
1258
|
+
if (startingDirectory !== void 0) {
|
|
1259
|
+
const error = validateStartingDirectory(startingDirectory);
|
|
1260
|
+
if (error) {
|
|
1261
|
+
log.warn(pc.yellow(`Ignoring the saved starting directory: ${error}`));
|
|
1262
|
+
} else {
|
|
1263
|
+
answers.startingDirectory = normalizeStartingDirectory(startingDirectory);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
const powerShellProfiles = getWindowsPowerShellProfiles(content);
|
|
1268
|
+
if (powerShellProfiles.length > 0 && powerShellProfiles.every(
|
|
1269
|
+
(profile) => /(?:^|\s)-NoLogo(?:\s|$)/i.test(profile.commandline ?? "")
|
|
1270
|
+
)) {
|
|
1271
|
+
answers.hidePowerShellBanner = true;
|
|
1272
|
+
}
|
|
1273
|
+
return answers;
|
|
1274
|
+
}
|
|
1275
|
+
async function applySettings(answers, settingsPath = findSettingsPath()) {
|
|
1276
|
+
const terminalIconsEnabled = answers.terminalIcons === true;
|
|
1277
|
+
const hidePowerShellBanner = answers.hidePowerShellBanner === true;
|
|
1278
|
+
if (!settingsPath) {
|
|
1279
|
+
if (terminalIconsEnabled) {
|
|
1280
|
+
throw new Error(
|
|
1281
|
+
"Cannot enable PowerShell file icons because Windows Terminal settings.json could not be found."
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
if (hidePowerShellBanner) {
|
|
1285
|
+
throw new Error(
|
|
1286
|
+
"Cannot hide the PowerShell startup banner because Windows Terminal settings.json could not be found."
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
log.warn(
|
|
1290
|
+
pc.dim("Could not find Windows Terminal settings.json. Skipping write.")
|
|
1291
|
+
);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
log.info(pc.dim(`Writing to ${settingsPath}\u2026`));
|
|
1295
|
+
const originalContent = readSettingsFile(settingsPath);
|
|
1296
|
+
let content = originalContent;
|
|
1297
|
+
for (const setting of settings) {
|
|
1298
|
+
const value = answers[setting.id];
|
|
1299
|
+
if (value === void 0) continue;
|
|
1300
|
+
if (!setting.jsonPath) continue;
|
|
1301
|
+
if (setting.skipIfKeep && value === "keep") continue;
|
|
1302
|
+
if (terminalIconsEnabled && setting.id === "fontFace") continue;
|
|
1303
|
+
if (setting.id === "transparency") {
|
|
1304
|
+
const opacity = value === "off" ? 100 : Number(value);
|
|
1305
|
+
if (!Number.isInteger(opacity) || opacity < 0 || opacity > 100) {
|
|
1306
|
+
throw new Error(`Invalid transparency value: ${String(value)}`);
|
|
1307
|
+
}
|
|
1308
|
+
content = setSetting(content, setting.jsonPath, opacity);
|
|
1309
|
+
content = setSetting(
|
|
1310
|
+
content,
|
|
1311
|
+
["profiles", "defaults", "useAcrylic"],
|
|
1312
|
+
value !== "off"
|
|
1313
|
+
);
|
|
1314
|
+
content = setSetting(
|
|
1315
|
+
content,
|
|
1316
|
+
["useAcrylicInTabRow"],
|
|
1317
|
+
value !== "off"
|
|
1318
|
+
);
|
|
1319
|
+
} else if (setting.id === "background") {
|
|
1320
|
+
content = setSetting(content, setting.jsonPath, value);
|
|
1321
|
+
content = setSetting(
|
|
1322
|
+
content,
|
|
1323
|
+
["profiles", "defaults", "tabColor"],
|
|
1324
|
+
value
|
|
1325
|
+
);
|
|
1326
|
+
} else if (setting.id === "fontFace") {
|
|
1327
|
+
const defaultProfileIndex = getDefaultProfileIndex(content);
|
|
1328
|
+
content = setSetting(content, setting.jsonPath, value);
|
|
1329
|
+
if (defaultProfileIndex !== void 0) {
|
|
1330
|
+
content = setSetting(
|
|
1331
|
+
content,
|
|
1332
|
+
["profiles", "list", defaultProfileIndex, "font", "face"],
|
|
1333
|
+
value
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
} else if (setting.id === "startingDirectory") {
|
|
1337
|
+
const defaultProfileIndex = getDefaultProfileIndex(content);
|
|
1338
|
+
const startingDirectory = String(value).trim() || void 0;
|
|
1339
|
+
content = setSetting(content, setting.jsonPath, startingDirectory);
|
|
1340
|
+
if (defaultProfileIndex !== void 0) {
|
|
1341
|
+
content = setSetting(
|
|
1342
|
+
content,
|
|
1343
|
+
["profiles", "list", defaultProfileIndex, "startingDirectory"],
|
|
1344
|
+
startingDirectory
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
} else {
|
|
1348
|
+
content = setSetting(content, setting.jsonPath, value);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
if (hidePowerShellBanner) {
|
|
1352
|
+
const powerShellProfiles = getWindowsPowerShellProfiles(content);
|
|
1353
|
+
if (powerShellProfiles.length === 0) {
|
|
1354
|
+
throw new Error(
|
|
1355
|
+
"Cannot hide the PowerShell startup banner because no Windows PowerShell profile was found."
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
for (const profile of powerShellProfiles) {
|
|
1359
|
+
const commandline = profile.commandline?.trim() ? profile.commandline : "powershell.exe";
|
|
1360
|
+
if (/(?:^|\s)-NoLogo(?:\s|$)/i.test(commandline)) continue;
|
|
1361
|
+
content = setSetting(
|
|
1362
|
+
content,
|
|
1363
|
+
["profiles", "list", profile.index, "commandline"],
|
|
1364
|
+
`${commandline} -NoLogo`
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (terminalIconsEnabled) {
|
|
1369
|
+
const defaultProfileIndex = getDefaultProfileIndex(content);
|
|
1370
|
+
content = setSetting(
|
|
1371
|
+
content,
|
|
1372
|
+
["profiles", "defaults", "font", "face"],
|
|
1373
|
+
NERD_FONT_FAMILY
|
|
1374
|
+
);
|
|
1375
|
+
if (defaultProfileIndex !== void 0) {
|
|
1376
|
+
content = setSetting(
|
|
1377
|
+
content,
|
|
1378
|
+
["profiles", "list", defaultProfileIndex, "font", "face"],
|
|
1379
|
+
NERD_FONT_FAMILY
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
if (terminalIconsEnabled) {
|
|
1384
|
+
await installNerdFont();
|
|
1385
|
+
installTerminalIconsModule();
|
|
1386
|
+
}
|
|
1387
|
+
const dirty = content !== originalContent;
|
|
1388
|
+
if (dirty) {
|
|
1389
|
+
const backupPath = createSettingsBackup(settingsPath);
|
|
1390
|
+
log.info(pc.dim(`Backup created at ${backupPath}`));
|
|
1391
|
+
writeSettingsFile(settingsPath, content);
|
|
1392
|
+
}
|
|
1393
|
+
if (terminalIconsEnabled) {
|
|
1394
|
+
addTerminalIconsToPowerShellProfile();
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
async function runInstaller() {
|
|
1398
|
+
console.log(pc.cyan(BANNER));
|
|
1399
|
+
intro(pc.bold("Pimp up your Terminal."));
|
|
1400
|
+
const selectableSettings = settings.filter(
|
|
1401
|
+
(setting) => setting.group === "Appearance" && (setting.jsonPath !== void 0 || setting.id === "terminalIcons" || setting.id === "hidePowerShellBanner") || setting.group === "Features" && (setting.jsonPath !== void 0 || setting.id === "historyPreview" || setting.id === "treeListing")
|
|
1402
|
+
);
|
|
1403
|
+
const settingsPath = findSettingsPath();
|
|
1404
|
+
const backups = settingsPath ? listSettingsBackups(settingsPath) : [];
|
|
1405
|
+
const answers = readConfiguredAnswers(settingsPath);
|
|
1406
|
+
let initialPickerValue = selectableSettings[0]?.id;
|
|
1407
|
+
let initialArea = "Appearance";
|
|
1408
|
+
while (true) {
|
|
1409
|
+
const areaOptions = [
|
|
1410
|
+
{ value: "Appearance", label: "Appearance", hint: "Colors and profile settings" },
|
|
1411
|
+
{ value: "Features", label: "Features", hint: "Optional extras" },
|
|
1412
|
+
{ value: "Other", label: "Other", hint: "Backups and recovery" }
|
|
1413
|
+
];
|
|
1414
|
+
if (Object.keys(answers).length > 0) {
|
|
1415
|
+
areaOptions.push({
|
|
1416
|
+
value: "done",
|
|
1417
|
+
label: "Done",
|
|
1418
|
+
hint: "Review and apply your settings"
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
const selectedArea = bail(
|
|
1422
|
+
await select({
|
|
1423
|
+
message: "Choose an area",
|
|
1424
|
+
options: areaOptions,
|
|
1425
|
+
initialValue: initialArea
|
|
1426
|
+
})
|
|
1427
|
+
);
|
|
1428
|
+
if (selectedArea === "done") break;
|
|
1429
|
+
initialArea = selectedArea;
|
|
1430
|
+
if (selectedArea === "Other") {
|
|
1431
|
+
if (backups.length === 0) {
|
|
1432
|
+
note(
|
|
1433
|
+
"No backups yet. pimpt creates one before changing settings.json.",
|
|
1434
|
+
"Other"
|
|
1435
|
+
);
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
const selectedAction = bail(
|
|
1439
|
+
await select({
|
|
1440
|
+
message: "Other",
|
|
1441
|
+
options: [
|
|
1442
|
+
{
|
|
1443
|
+
value: "restoreBackup",
|
|
1444
|
+
label: "Restore settings backup",
|
|
1445
|
+
hint: "Choose a saved version"
|
|
1446
|
+
},
|
|
1447
|
+
{ value: "back", label: "Back", hint: "Choose another area" }
|
|
1448
|
+
],
|
|
1449
|
+
initialValue: "restoreBackup"
|
|
1450
|
+
})
|
|
1451
|
+
);
|
|
1452
|
+
if (selectedAction === "back") continue;
|
|
1453
|
+
if (!settingsPath) {
|
|
1454
|
+
throw new Error("Windows Terminal settings.json could not be found.");
|
|
1455
|
+
}
|
|
1456
|
+
const selectedBackup = bail(
|
|
1457
|
+
await select({
|
|
1458
|
+
message: "Which backup should be restored?",
|
|
1459
|
+
options: backups.map((backup) => ({
|
|
1460
|
+
value: backup.path,
|
|
1461
|
+
label: backup.createdAt.toLocaleString(),
|
|
1462
|
+
hint: backup.path
|
|
1463
|
+
})),
|
|
1464
|
+
...backups[0] ? { initialValue: backups[0].path } : {}
|
|
1465
|
+
})
|
|
1466
|
+
);
|
|
1467
|
+
const shouldRestore = bail(
|
|
1468
|
+
await confirm({
|
|
1469
|
+
message: "This replaces settings.json. The current file will be backed up first. Continue?",
|
|
1470
|
+
initialValue: false
|
|
1471
|
+
})
|
|
1472
|
+
);
|
|
1473
|
+
if (!shouldRestore) continue;
|
|
1474
|
+
const restoreSpinner = spinner();
|
|
1475
|
+
restoreSpinner.start("Restoring settings.json backup\u2026");
|
|
1476
|
+
try {
|
|
1477
|
+
const safetyBackupPath = restoreSettingsBackup(
|
|
1478
|
+
settingsPath,
|
|
1479
|
+
selectedBackup
|
|
1480
|
+
);
|
|
1481
|
+
restoreSpinner.stop("Settings restored successfully.");
|
|
1482
|
+
log.info(
|
|
1483
|
+
pc.dim(`The previous settings were backed up to ${safetyBackupPath}`)
|
|
1484
|
+
);
|
|
1485
|
+
outro(pc.green("\u2713 Windows Terminal settings.json was restored."));
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
restoreSpinner.error("Could not restore the settings backup.");
|
|
1488
|
+
throw error;
|
|
1489
|
+
}
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
while (true) {
|
|
1493
|
+
const visibleSettings = selectableSettings.filter(
|
|
1494
|
+
(setting2) => setting2.group === selectedArea && !(answers.terminalIcons === true && setting2.id === "fontFace")
|
|
1495
|
+
);
|
|
1496
|
+
const options = visibleSettings.map((setting2) => ({
|
|
1497
|
+
value: setting2.id,
|
|
1498
|
+
label: answers[setting2.id] === void 0 ? setting2.label : `${pc.green("\u25CF")} ${setting2.label}`,
|
|
1499
|
+
hint: setting2.hint
|
|
1500
|
+
}));
|
|
1501
|
+
options.push({
|
|
1502
|
+
value: "back",
|
|
1503
|
+
label: "Back",
|
|
1504
|
+
hint: "Choose another area"
|
|
1505
|
+
});
|
|
1506
|
+
const firstOption = options[0];
|
|
1507
|
+
if (!firstOption) {
|
|
1508
|
+
throw new Error(`No settings are available in ${selectedArea}.`);
|
|
1509
|
+
}
|
|
1510
|
+
const selectedInitialValue = initialPickerValue && options.some((option) => option.value === initialPickerValue) ? initialPickerValue : firstOption.value;
|
|
1511
|
+
const selectedId = bail(
|
|
1512
|
+
await select({
|
|
1513
|
+
message: selectedArea,
|
|
1514
|
+
options,
|
|
1515
|
+
initialValue: selectedInitialValue
|
|
1516
|
+
})
|
|
1517
|
+
);
|
|
1518
|
+
if (selectedId === "back") break;
|
|
1519
|
+
const setting = selectableSettings.find(
|
|
1520
|
+
(candidate) => candidate.id === selectedId && candidate.group === selectedArea
|
|
1521
|
+
);
|
|
1522
|
+
if (!setting) {
|
|
1523
|
+
throw new Error(
|
|
1524
|
+
`Unknown setting selected in ${selectedArea}: ${selectedId}`
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
initialPickerValue = selectedId;
|
|
1528
|
+
log.step(pc.bold(pc.cyan(setting.group)));
|
|
1529
|
+
answers[setting.id] = await askSetting(setting, answers[setting.id]);
|
|
1530
|
+
if (setting.id === "terminalIcons" && answers.terminalIcons === true) {
|
|
1531
|
+
answers.fontFace = NERD_FONT_FAMILY;
|
|
1532
|
+
initialPickerValue = "terminalIcons";
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
renderSummary(answers);
|
|
1537
|
+
const proceed = bail(
|
|
1538
|
+
await confirm({
|
|
1539
|
+
message: "Install these settings? (writes to settings.json)",
|
|
1540
|
+
initialValue: true
|
|
1541
|
+
})
|
|
1542
|
+
);
|
|
1543
|
+
if (!proceed) {
|
|
1544
|
+
cancel("Setup cancelled. Nothing was changed.");
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const s = spinner();
|
|
1548
|
+
s.start("Building your configuration\u2026");
|
|
1549
|
+
await delay(300);
|
|
1550
|
+
s.message("Checking compatibility\u2026");
|
|
1551
|
+
await delay(300);
|
|
1552
|
+
s.message(
|
|
1553
|
+
answers.terminalIcons === true || answers.historyPreview === true || answers.treeListing === true || answers.hidePowerShellBanner === true ? "Applying PowerShell options\u2026" : "Writing settings.json\u2026"
|
|
1554
|
+
);
|
|
1555
|
+
await delay(400);
|
|
1556
|
+
try {
|
|
1557
|
+
await applySettings(answers, settingsPath);
|
|
1558
|
+
if (answers.historyPreview === true) {
|
|
1559
|
+
installPSReadLineModule();
|
|
1560
|
+
addHistoryPreviewToPowerShellProfile();
|
|
1561
|
+
}
|
|
1562
|
+
if (answers.treeListing === true) {
|
|
1563
|
+
addTreeListingToPowerShellProfile();
|
|
1564
|
+
}
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
if (!(error instanceof InvalidSettingsFileError)) throw error;
|
|
1567
|
+
s.error(error.message);
|
|
1568
|
+
process.exitCode = 1;
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
s.stop("Configuration applied successfully.");
|
|
1572
|
+
const fontSelected = answers.fontFace !== void 0 && answers.fontFace !== "keep";
|
|
1573
|
+
const powerShellFeatureSelected = answers.terminalIcons === true || answers.historyPreview === true || answers.treeListing === true || answers.hidePowerShellBanner === true;
|
|
1574
|
+
if (fontSelected || powerShellFeatureSelected) openNewTerminalTab();
|
|
1575
|
+
outro(
|
|
1576
|
+
[
|
|
1577
|
+
`${pc.green("\u2713")} Your configuration is ready.`,
|
|
1578
|
+
pc.dim("Your selected settings and features are ready.")
|
|
1579
|
+
].join("\n")
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/index.ts
|
|
1584
|
+
function readVersion() {
|
|
1585
|
+
const pkg = JSON.parse(
|
|
1586
|
+
readFileSync2(new URL("../package.json", import.meta.url), "utf8")
|
|
1587
|
+
);
|
|
1588
|
+
return pkg.version;
|
|
1589
|
+
}
|
|
1590
|
+
function printHelp() {
|
|
1591
|
+
console.log(`
|
|
1592
|
+
${pc2.bold(pc2.cyan("pimpt"))} ${pc2.dim("\u2014 interactive Windows Terminal setup assistant")}
|
|
1593
|
+
|
|
1594
|
+
${pc2.bold("Usage")}
|
|
1595
|
+
pimpt Start the interactive installer
|
|
1596
|
+
pimpt --help Show this help
|
|
1597
|
+
pimpt --version Show the installed version
|
|
1598
|
+
|
|
1599
|
+
${pc2.bold("Status")}
|
|
1600
|
+
Preview: preferences are collected, but settings.json is not modified yet.
|
|
1601
|
+
`);
|
|
1602
|
+
}
|
|
1603
|
+
async function main() {
|
|
1604
|
+
const args = process.argv.slice(2);
|
|
1605
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1606
|
+
printHelp();
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
1610
|
+
console.log(`pimpt ${readVersion()}`);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
if (args.length > 0) {
|
|
1614
|
+
console.error(`Unknown option: ${args[0]}`);
|
|
1615
|
+
printHelp();
|
|
1616
|
+
process.exitCode = 1;
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
await runInstaller();
|
|
1620
|
+
}
|
|
1621
|
+
try {
|
|
1622
|
+
await main();
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
console.error(pc2.red("pimpt failed:"));
|
|
1625
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
1626
|
+
process.exitCode = 1;
|
|
1627
|
+
}
|
|
1628
|
+
//# sourceMappingURL=index.js.map
|