deskify-cli 1.0.0 → 1.0.1
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 +27 -17
- package/deskify.js +9 -893
- package/package.json +2 -2
- package/src/flows/create.js +284 -0
- package/src/flows/uninstall.js +145 -0
- package/src/utils/colors.js +46 -0
- package/src/utils/favicon.js +196 -0
- package/src/utils/prompts.js +182 -0
- package/src/utils/spinner.js +41 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prompts.js
|
|
3
|
+
* Interactive keyboard prompts for the deskify CLI.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { colors } = require('./colors');
|
|
9
|
+
|
|
10
|
+
function expandHome(pathStr) {
|
|
11
|
+
if (!pathStr) return '';
|
|
12
|
+
if (pathStr.startsWith('~/') || pathStr === '~') {
|
|
13
|
+
return pathStr.replace('~', os.homedir());
|
|
14
|
+
}
|
|
15
|
+
return pathStr;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ask(questionText, defaultValue = '') {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const prompt = defaultValue
|
|
21
|
+
? `${colors.bright}${questionText}${colors.reset} ${colors.dim}(default: ${defaultValue})${colors.reset}: `
|
|
22
|
+
: `${colors.bright}${questionText}${colors.reset}: `;
|
|
23
|
+
|
|
24
|
+
process.stdout.write(prompt);
|
|
25
|
+
|
|
26
|
+
let buffer = '';
|
|
27
|
+
const rl = readline.createInterface({
|
|
28
|
+
input: process.stdin,
|
|
29
|
+
output: process.stdout
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
33
|
+
if (process.stdin.isTTY) {
|
|
34
|
+
process.stdin.setRawMode(true);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onKeypress(str, key) {
|
|
38
|
+
if (key) {
|
|
39
|
+
if (key.name === 'escape') {
|
|
40
|
+
cleanup();
|
|
41
|
+
reject(new Error('ESC'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (key.ctrl && key.name === 'c') {
|
|
45
|
+
cleanup();
|
|
46
|
+
process.stdout.write('\n');
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
50
|
+
cleanup();
|
|
51
|
+
process.stdout.write('\n');
|
|
52
|
+
resolve(buffer.trim() || defaultValue);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (key.name === 'backspace') {
|
|
56
|
+
if (buffer.length > 0) {
|
|
57
|
+
buffer = buffer.slice(0, -1);
|
|
58
|
+
process.stdout.write('\r\x1B[2K');
|
|
59
|
+
process.stdout.write(prompt + buffer);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
|
66
|
+
buffer += str;
|
|
67
|
+
process.stdout.write(str);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cleanup() {
|
|
72
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
73
|
+
if (process.stdin.isTTY) {
|
|
74
|
+
process.stdin.setRawMode(false);
|
|
75
|
+
}
|
|
76
|
+
rl.close();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process.stdin.on('keypress', onKeypress);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function askYesNo(questionText, defaultYes = true) {
|
|
84
|
+
const defaultStr = defaultYes ? 'Y/n' : 'y/N';
|
|
85
|
+
const answer = await ask(`${questionText} (${defaultStr})`);
|
|
86
|
+
if (!answer) return defaultYes;
|
|
87
|
+
return answer.toLowerCase().startsWith('y');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function selectOption(questionText, options, defaultIndex = 0) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
let selectedIndex = defaultIndex;
|
|
93
|
+
const rl = readline.createInterface({
|
|
94
|
+
input: process.stdin,
|
|
95
|
+
output: process.stdout
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
99
|
+
if (process.stdin.isTTY) {
|
|
100
|
+
process.stdin.setRawMode(true);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
process.stdout.write('\x1B[?25l');
|
|
104
|
+
|
|
105
|
+
function renderMenu() {
|
|
106
|
+
console.log(`\n${colors.bright}${colors.cyan}❯${colors.reset} ${colors.bright}${questionText}${colors.reset}`);
|
|
107
|
+
options.forEach((opt, idx) => {
|
|
108
|
+
if (idx === selectedIndex) {
|
|
109
|
+
console.log(` ${colors.cyan}${colors.bright}❯ ${opt}${colors.reset}`);
|
|
110
|
+
} else {
|
|
111
|
+
console.log(` ${colors.dim}${opt}${colors.reset}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
renderMenu();
|
|
117
|
+
|
|
118
|
+
function onKeypress(str, key) {
|
|
119
|
+
const linesToMove = options.length + 2;
|
|
120
|
+
process.stdout.write(`\x1B[${linesToMove}A`);
|
|
121
|
+
for (let i = 0; i < linesToMove; i++) {
|
|
122
|
+
process.stdout.write('\x1B[2K\n');
|
|
123
|
+
}
|
|
124
|
+
process.stdout.write(`\x1B[${linesToMove}A`);
|
|
125
|
+
|
|
126
|
+
if (key) {
|
|
127
|
+
if (key.name === 'up') {
|
|
128
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
129
|
+
} else if (key.name === 'down') {
|
|
130
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
131
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
132
|
+
cleanup();
|
|
133
|
+
resolve(selectedIndex);
|
|
134
|
+
return;
|
|
135
|
+
} else if (key.name === 'escape') {
|
|
136
|
+
cleanup();
|
|
137
|
+
resolve(options.length - 1);
|
|
138
|
+
return;
|
|
139
|
+
} else if (key.ctrl && key.name === 'c') {
|
|
140
|
+
cleanup();
|
|
141
|
+
process.stdout.write('\n');
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
renderMenu();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function cleanup() {
|
|
149
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
150
|
+
if (process.stdin.isTTY) {
|
|
151
|
+
process.stdin.setRawMode(false);
|
|
152
|
+
}
|
|
153
|
+
process.stdout.write('\x1B[?25h');
|
|
154
|
+
rl.close();
|
|
155
|
+
|
|
156
|
+
const linesToMove = options.length + 2;
|
|
157
|
+
process.stdout.write(`\x1B[${linesToMove}A`);
|
|
158
|
+
for (let i = 0; i < linesToMove; i++) {
|
|
159
|
+
process.stdout.write('\x1B[2K\n');
|
|
160
|
+
}
|
|
161
|
+
process.stdout.write(`\x1B[${linesToMove}A`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
process.stdin.on('keypress', onKeypress);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function waitForKey() {
|
|
169
|
+
try {
|
|
170
|
+
await ask('Press Enter to return to the Main Menu...');
|
|
171
|
+
} catch (e) {
|
|
172
|
+
// Silently consume ESC or any other rejection to return to main menu
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
expandHome,
|
|
178
|
+
ask,
|
|
179
|
+
askYesNo,
|
|
180
|
+
selectOption,
|
|
181
|
+
waitForKey
|
|
182
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spinner.js
|
|
3
|
+
* Loading spinners for the deskify CLI.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { colors } = require('./colors');
|
|
7
|
+
|
|
8
|
+
let spinnerInterval = null;
|
|
9
|
+
|
|
10
|
+
function startSpinner(msg) {
|
|
11
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
12
|
+
let i = 0;
|
|
13
|
+
process.stdout.write('\x1B[?25l');
|
|
14
|
+
process.stdout.write(`${colors.cyan}${frames[0]}${colors.reset} ${msg}`);
|
|
15
|
+
spinnerInterval = setInterval(() => {
|
|
16
|
+
i = (i + 1) % frames.length;
|
|
17
|
+
process.stdout.write(`\r${colors.cyan}${frames[i]}${colors.reset} ${msg}`);
|
|
18
|
+
}, 80);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function stopSpinner(success = true, statusMsg = '') {
|
|
22
|
+
if (spinnerInterval) {
|
|
23
|
+
clearInterval(spinnerInterval);
|
|
24
|
+
spinnerInterval = null;
|
|
25
|
+
}
|
|
26
|
+
process.stdout.write('\r\x1B[2K');
|
|
27
|
+
process.stdout.write('\x1B[?25h');
|
|
28
|
+
|
|
29
|
+
if (statusMsg) {
|
|
30
|
+
if (success) {
|
|
31
|
+
console.log(`${colors.green}✔${colors.reset} ${statusMsg}`);
|
|
32
|
+
} else {
|
|
33
|
+
console.log(`${colors.red}✖${colors.reset} ${statusMsg}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
startSpinner,
|
|
40
|
+
stopSpinner
|
|
41
|
+
};
|