sunnah 1.0.0 → 1.0.3
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.
Potentially problematic release.
This version of sunnah might be problematic. Click here for more details.
- package/bin/index.js +479 -0
- package/package.json +1 -1
package/bin/index.js
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import { execSync, spawn } from "child_process";
|
|
7
|
+
import readline from "readline";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const pkg = JSON.parse(
|
|
12
|
+
fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
// ── Colors ────────────────────────────────────────────────────────────────────
|
|
16
|
+
const c = {
|
|
17
|
+
reset: "\x1b[0m",
|
|
18
|
+
bold: "\x1b[1m",
|
|
19
|
+
dim: "\x1b[2m",
|
|
20
|
+
green: "\x1b[32m",
|
|
21
|
+
yellow: "\x1b[33m",
|
|
22
|
+
cyan: "\x1b[36m",
|
|
23
|
+
magenta: "\x1b[35m",
|
|
24
|
+
blue: "\x1b[34m",
|
|
25
|
+
red: "\x1b[31m",
|
|
26
|
+
gray: "\x1b[90m",
|
|
27
|
+
white: "\x1b[97m",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const clr = (color, text) => `${color}${text}${c.reset}`;
|
|
31
|
+
const bold = (t) => clr(c.bold, t);
|
|
32
|
+
const green = (t) => clr(c.green, t);
|
|
33
|
+
const yellow = (t) => clr(c.yellow, t);
|
|
34
|
+
const cyan = (t) => clr(c.cyan, t);
|
|
35
|
+
const magenta = (t) => clr(c.magenta, t);
|
|
36
|
+
const gray = (t) => clr(c.gray, t);
|
|
37
|
+
const red = (t) => clr(c.red, t);
|
|
38
|
+
const dim = (t) => clr(c.dim, t);
|
|
39
|
+
const white = (t) => clr(c.white, t);
|
|
40
|
+
|
|
41
|
+
// ── Available packages ────────────────────────────────────────────────────────
|
|
42
|
+
const PACKAGES = [
|
|
43
|
+
{
|
|
44
|
+
name: "sahih-al-bukhari",
|
|
45
|
+
label: "Sahih al-Bukhari",
|
|
46
|
+
author: "Imam Muhammad ibn Ismail al-Bukhari",
|
|
47
|
+
desc: "The most authentic collection of hadith, widely regarded as the most sahih after the Quran.",
|
|
48
|
+
hadiths: "7,563",
|
|
49
|
+
cmd: "bukhari",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "sahih-muslim",
|
|
53
|
+
label: "Sahih Muslim",
|
|
54
|
+
author: "Imam Muslim ibn al-Hajjaj",
|
|
55
|
+
desc: "Second most authentic hadith collection, known for its strict methodology and chain verification.",
|
|
56
|
+
hadiths: "7,470",
|
|
57
|
+
cmd: "muslim",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "sunan-abi-dawud",
|
|
61
|
+
label: "Sunan Abi Dawud",
|
|
62
|
+
author: "Imam Abu Dawud Sulayman ibn al-Ash'ath",
|
|
63
|
+
desc: "One of the six canonical hadith collections, focused on legal rulings and jurisprudence.",
|
|
64
|
+
hadiths: "5,274",
|
|
65
|
+
cmd: "dawud",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "jami-al-tirmidhi",
|
|
69
|
+
label: "Jami al-Tirmidhi",
|
|
70
|
+
author: "Imam Abu Isa Muhammad al-Tirmidhi",
|
|
71
|
+
desc: "Part of the six major hadith collections, unique for grading each hadith's authenticity.",
|
|
72
|
+
hadiths: "3,956",
|
|
73
|
+
cmd: "tirmidhi",
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// ── Terminal helpers ──────────────────────────────────────────────────────────
|
|
78
|
+
const W = () => process.stdout.columns || 80;
|
|
79
|
+
|
|
80
|
+
function clearLine() {
|
|
81
|
+
process.stdout.write("\r\x1b[K");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function moveUp(n) {
|
|
85
|
+
if (n > 0) process.stdout.write(`\x1b[${n}A`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hideCursor() {
|
|
89
|
+
process.stdout.write("\x1b[?25l");
|
|
90
|
+
}
|
|
91
|
+
function showCursor() {
|
|
92
|
+
process.stdout.write("\x1b[?25h");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Progress bar ──────────────────────────────────────────────────────────────
|
|
96
|
+
function drawBar(label, percent, barWidth = 38) {
|
|
97
|
+
const filled = Math.round((percent / 100) * barWidth);
|
|
98
|
+
const empty = barWidth - filled;
|
|
99
|
+
const bar =
|
|
100
|
+
c.green + "█".repeat(filled) + c.gray + "░".repeat(empty) + c.reset;
|
|
101
|
+
const pct = cyan(String(Math.round(percent)).padStart(3) + "%");
|
|
102
|
+
return ` ${bar} ${pct} ${dim(label)}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Install a package with animated progress bar ──────────────────────────────
|
|
106
|
+
function animateInstall(pkgName) {
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
const stages = [
|
|
109
|
+
{ label: "Resolving packages…", end: 12, ms: 90 },
|
|
110
|
+
{ label: "Fetching metadata…", end: 28, ms: 70 },
|
|
111
|
+
{ label: "Downloading tarball…", end: 72, ms: 25 },
|
|
112
|
+
{ label: "Extracting files…", end: 88, ms: 55 },
|
|
113
|
+
{ label: "Linking binaries…", end: 98, ms: 90 },
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
let percent = 0;
|
|
117
|
+
let stageIdx = 0;
|
|
118
|
+
let npmDone = false;
|
|
119
|
+
|
|
120
|
+
// Print initial bar on its own line
|
|
121
|
+
process.stdout.write(drawBar(stages[0].label, 0) + "\n");
|
|
122
|
+
|
|
123
|
+
const tick = () => {
|
|
124
|
+
const stage = stages[stageIdx];
|
|
125
|
+
if (!stage) return;
|
|
126
|
+
|
|
127
|
+
const prevEnd = stageIdx > 0 ? stages[stageIdx - 1].end : 0;
|
|
128
|
+
const step = (stage.end - prevEnd) / 22;
|
|
129
|
+
percent = Math.min(percent + step, stage.end);
|
|
130
|
+
|
|
131
|
+
// Overwrite the bar line
|
|
132
|
+
process.stdout.write("\r\x1b[K");
|
|
133
|
+
process.stdout.write(drawBar(stage.label, percent));
|
|
134
|
+
|
|
135
|
+
if (percent >= stage.end) {
|
|
136
|
+
stageIdx++;
|
|
137
|
+
if (stageIdx >= stages.length) {
|
|
138
|
+
// All visual stages done — wait for npm
|
|
139
|
+
const poll = setInterval(() => {
|
|
140
|
+
if (npmDone) {
|
|
141
|
+
clearInterval(poll);
|
|
142
|
+
process.stdout.write("\r\x1b[K");
|
|
143
|
+
process.stdout.write(drawBar("Complete!", 100));
|
|
144
|
+
process.stdout.write("\n");
|
|
145
|
+
resolve();
|
|
146
|
+
}
|
|
147
|
+
}, 80);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
setTimeout(tick, stages[stageIdx]?.ms ?? 50);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
setTimeout(tick, stages[0].ms);
|
|
156
|
+
|
|
157
|
+
// Actually run npm install -g
|
|
158
|
+
const proc = spawn("npm", ["install", "-g", pkgName], {
|
|
159
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
160
|
+
});
|
|
161
|
+
proc.on("close", () => {
|
|
162
|
+
npmDone = true;
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Check if a package is already installed globally ─────────────────────────
|
|
168
|
+
function isInstalled(name) {
|
|
169
|
+
try {
|
|
170
|
+
execSync(`npm list -g ${name} --depth=0 2>/dev/null`, { stdio: "ignore" });
|
|
171
|
+
return true;
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Render the interactive list ───────────────────────────────────────────────
|
|
178
|
+
const DIV_W = () => Math.min(W() - 2, 70);
|
|
179
|
+
|
|
180
|
+
function renderList(selected, cursor) {
|
|
181
|
+
const div = gray("─".repeat(DIV_W()));
|
|
182
|
+
const div2 = gray("═".repeat(DIV_W()));
|
|
183
|
+
const lines = [];
|
|
184
|
+
|
|
185
|
+
lines.push("");
|
|
186
|
+
lines.push(div2);
|
|
187
|
+
lines.push(
|
|
188
|
+
bold(cyan(" 📚 Sunnah Package Manager")) + gray(" v" + pkg.version),
|
|
189
|
+
);
|
|
190
|
+
lines.push(
|
|
191
|
+
gray(" ↑↓ navigate ") +
|
|
192
|
+
gray("space select ") +
|
|
193
|
+
gray("a all ") +
|
|
194
|
+
gray("enter install ") +
|
|
195
|
+
gray("q quit"),
|
|
196
|
+
);
|
|
197
|
+
lines.push(div2);
|
|
198
|
+
lines.push("");
|
|
199
|
+
|
|
200
|
+
PACKAGES.forEach((p, i) => {
|
|
201
|
+
const isCursor = i === cursor;
|
|
202
|
+
const isSelected = selected.has(i);
|
|
203
|
+
const installed = isInstalled(p.name);
|
|
204
|
+
|
|
205
|
+
const checkbox = isSelected ? green("[✓]") : gray("[ ]");
|
|
206
|
+
const arrow = isCursor ? cyan("▶") : " ";
|
|
207
|
+
const label = isCursor
|
|
208
|
+
? bold(white(p.label))
|
|
209
|
+
: isSelected
|
|
210
|
+
? green(p.label)
|
|
211
|
+
: white(p.label);
|
|
212
|
+
const badge = installed ? dim(gray(" (installed)")) : "";
|
|
213
|
+
|
|
214
|
+
lines.push(` ${arrow} ${checkbox} ${label}${badge}`);
|
|
215
|
+
|
|
216
|
+
if (isCursor) {
|
|
217
|
+
lines.push(` ${dim(p.author)}`);
|
|
218
|
+
lines.push(` ${gray(p.desc)}`);
|
|
219
|
+
lines.push(
|
|
220
|
+
` ${gray("Hadiths: ")}${yellow(p.hadiths)} ${gray("CLI: ")}${cyan(p.cmd + " --help")}`,
|
|
221
|
+
);
|
|
222
|
+
lines.push("");
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
lines.push("");
|
|
227
|
+
lines.push(div);
|
|
228
|
+
|
|
229
|
+
const count = selected.size;
|
|
230
|
+
if (count > 0) {
|
|
231
|
+
const names = [...selected].map((i) => cyan(PACKAGES[i].name)).join(", ");
|
|
232
|
+
lines.push(` ${green("●")} ${bold(String(count))} selected: ${names}`);
|
|
233
|
+
} else {
|
|
234
|
+
lines.push(
|
|
235
|
+
` ${gray("Nothing selected — press space to select a package")}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
lines.push(div);
|
|
239
|
+
lines.push("");
|
|
240
|
+
|
|
241
|
+
return lines;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function printLines(lines) {
|
|
245
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function eraseLines(n) {
|
|
249
|
+
for (let i = 0; i < n; i++) {
|
|
250
|
+
clearLine();
|
|
251
|
+
if (i < n - 1) moveUp(1);
|
|
252
|
+
}
|
|
253
|
+
clearLine();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
257
|
+
async function main() {
|
|
258
|
+
const rawArgs = process.argv.slice(2);
|
|
259
|
+
const flags = rawArgs.filter((a) => a.startsWith("-"));
|
|
260
|
+
|
|
261
|
+
// --version
|
|
262
|
+
if (flags.some((f) => f === "-v" || f === "--version")) {
|
|
263
|
+
console.log("");
|
|
264
|
+
console.log(" " + bold(cyan("sunnah")) + gray(" v" + pkg.version));
|
|
265
|
+
console.log(
|
|
266
|
+
" " + gray("Available packages: ") + yellow(String(PACKAGES.length)),
|
|
267
|
+
);
|
|
268
|
+
console.log("");
|
|
269
|
+
process.exit(0);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --help
|
|
273
|
+
if (flags.some((f) => f === "-h" || f === "--help")) {
|
|
274
|
+
const div = gray("─".repeat(60));
|
|
275
|
+
console.log("");
|
|
276
|
+
console.log(div);
|
|
277
|
+
console.log(
|
|
278
|
+
bold(cyan(" Sunnah Package Manager")) + gray(" v" + pkg.version),
|
|
279
|
+
);
|
|
280
|
+
console.log(div);
|
|
281
|
+
console.log("");
|
|
282
|
+
console.log(" " + bold("Usage:"));
|
|
283
|
+
console.log(
|
|
284
|
+
" " +
|
|
285
|
+
cyan("sunnah") +
|
|
286
|
+
gray(" Open interactive installer"),
|
|
287
|
+
);
|
|
288
|
+
console.log(
|
|
289
|
+
" " +
|
|
290
|
+
cyan("sunnah") +
|
|
291
|
+
green(" --list") +
|
|
292
|
+
gray(" List all available packages"),
|
|
293
|
+
);
|
|
294
|
+
console.log(
|
|
295
|
+
" " + cyan("sunnah") + green(" -v") + gray(" Show version"),
|
|
296
|
+
);
|
|
297
|
+
console.log(
|
|
298
|
+
" " +
|
|
299
|
+
cyan("sunnah") +
|
|
300
|
+
green(" -h") +
|
|
301
|
+
gray(" Show this help"),
|
|
302
|
+
);
|
|
303
|
+
console.log("");
|
|
304
|
+
console.log(" " + bold("Controls (interactive mode):"));
|
|
305
|
+
console.log(" " + green("↑ ↓") + gray(" Navigate packages"));
|
|
306
|
+
console.log(" " + green("space") + gray(" Toggle selection"));
|
|
307
|
+
console.log(" " + green("a") + gray(" Toggle all / deselect all"));
|
|
308
|
+
console.log(" " + green("enter") + gray(" Install selected packages"));
|
|
309
|
+
console.log(" " + green("q") + gray(" Quit"));
|
|
310
|
+
console.log("");
|
|
311
|
+
console.log(div);
|
|
312
|
+
console.log("");
|
|
313
|
+
process.exit(0);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// --list
|
|
317
|
+
if (flags.some((f) => f === "--list" || f === "-l")) {
|
|
318
|
+
const div = gray("─".repeat(60));
|
|
319
|
+
console.log("");
|
|
320
|
+
console.log(div);
|
|
321
|
+
console.log(bold(cyan(" Available Sunnah Packages")));
|
|
322
|
+
console.log(div);
|
|
323
|
+
PACKAGES.forEach((p) => {
|
|
324
|
+
const inst = isInstalled(p.name) ? green(" ✓ installed") : "";
|
|
325
|
+
console.log("");
|
|
326
|
+
console.log(` ${bold(white(p.label))}${inst}`);
|
|
327
|
+
console.log(` ${cyan("npm install -g " + p.name)}`);
|
|
328
|
+
console.log(` ${dim(p.desc)}`);
|
|
329
|
+
console.log(
|
|
330
|
+
` ${gray("Hadiths: ")}${yellow(p.hadiths)} ${gray("Author: ")}${magenta(p.author)}`,
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
console.log("");
|
|
334
|
+
console.log(div);
|
|
335
|
+
console.log("");
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ── Interactive mode ────────────────────────────────────────────────────────
|
|
340
|
+
if (!process.stdin.isTTY) {
|
|
341
|
+
console.error(red("\n ✗ Interactive mode requires a TTY terminal.\n"));
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
readline.emitKeypressEvents(process.stdin);
|
|
346
|
+
process.stdin.setRawMode(true);
|
|
347
|
+
hideCursor();
|
|
348
|
+
|
|
349
|
+
let cursor = 0;
|
|
350
|
+
let selected = new Set();
|
|
351
|
+
let prevCount = 0;
|
|
352
|
+
|
|
353
|
+
const draw = () => {
|
|
354
|
+
const lines = renderList(selected, cursor);
|
|
355
|
+
if (prevCount > 0) eraseLines(prevCount);
|
|
356
|
+
printLines(lines);
|
|
357
|
+
prevCount = lines.length;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
draw();
|
|
361
|
+
|
|
362
|
+
const cleanup = () => {
|
|
363
|
+
showCursor();
|
|
364
|
+
try {
|
|
365
|
+
process.stdin.setRawMode(false);
|
|
366
|
+
} catch {}
|
|
367
|
+
process.stdin.pause();
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
process.on("SIGINT", () => {
|
|
371
|
+
cleanup();
|
|
372
|
+
console.log("");
|
|
373
|
+
process.exit(0);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
process.stdin.on("keypress", async (str, key) => {
|
|
377
|
+
if (!key) return;
|
|
378
|
+
|
|
379
|
+
// Quit
|
|
380
|
+
if (key.name === "q" || (key.ctrl && key.name === "c")) {
|
|
381
|
+
cleanup();
|
|
382
|
+
if (prevCount > 0) eraseLines(prevCount);
|
|
383
|
+
console.log("\n " + gray("Goodbye.\n"));
|
|
384
|
+
process.exit(0);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Navigate
|
|
388
|
+
if (key.name === "up") {
|
|
389
|
+
cursor = (cursor - 1 + PACKAGES.length) % PACKAGES.length;
|
|
390
|
+
draw();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (key.name === "down") {
|
|
394
|
+
cursor = (cursor + 1) % PACKAGES.length;
|
|
395
|
+
draw();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Toggle selection
|
|
400
|
+
if (str === " ") {
|
|
401
|
+
if (selected.has(cursor)) selected.delete(cursor);
|
|
402
|
+
else selected.add(cursor);
|
|
403
|
+
draw();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Toggle all
|
|
408
|
+
if (str === "a" || str === "A") {
|
|
409
|
+
if (selected.size === PACKAGES.length) selected.clear();
|
|
410
|
+
else PACKAGES.forEach((_, i) => selected.add(i));
|
|
411
|
+
draw();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Install
|
|
416
|
+
if (key.name === "return") {
|
|
417
|
+
if (selected.size === 0) return;
|
|
418
|
+
|
|
419
|
+
cleanup();
|
|
420
|
+
if (prevCount > 0) eraseLines(prevCount);
|
|
421
|
+
|
|
422
|
+
const toInstall = [...selected].map((i) => PACKAGES[i]);
|
|
423
|
+
const total = toInstall.length;
|
|
424
|
+
const div = gray("─".repeat(DIV_W()));
|
|
425
|
+
const div2 = gray("═".repeat(DIV_W()));
|
|
426
|
+
|
|
427
|
+
console.log("");
|
|
428
|
+
console.log(div2);
|
|
429
|
+
console.log(
|
|
430
|
+
bold(cyan(" Installing ")) +
|
|
431
|
+
bold(yellow(String(total))) +
|
|
432
|
+
bold(cyan(" package" + (total > 1 ? "s" : "") + "…")),
|
|
433
|
+
);
|
|
434
|
+
console.log(div2);
|
|
435
|
+
|
|
436
|
+
for (let i = 0; i < toInstall.length; i++) {
|
|
437
|
+
const p = toInstall[i];
|
|
438
|
+
console.log("");
|
|
439
|
+
console.log(
|
|
440
|
+
` ${cyan("[" + (i + 1) + "/" + total + "]")} ${bold(white(p.label))}`,
|
|
441
|
+
);
|
|
442
|
+
console.log(` ${dim("npm install -g " + p.name)}`);
|
|
443
|
+
console.log("");
|
|
444
|
+
|
|
445
|
+
await animateInstall(p.name);
|
|
446
|
+
|
|
447
|
+
console.log(
|
|
448
|
+
` ${green("✓")} ${bold(green(p.label))} installed successfully`,
|
|
449
|
+
);
|
|
450
|
+
console.log(` ${gray("Usage: ")}${cyan(p.cmd + " --help")}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log("");
|
|
454
|
+
console.log(div2);
|
|
455
|
+
console.log(
|
|
456
|
+
` ${green("✓")} All done! ` +
|
|
457
|
+
bold(yellow(String(total))) +
|
|
458
|
+
` package${total > 1 ? "s" : ""} installed globally.`,
|
|
459
|
+
);
|
|
460
|
+
console.log("");
|
|
461
|
+
toInstall.forEach((p) => {
|
|
462
|
+
console.log(
|
|
463
|
+
` ${cyan("▸")} ${bold(p.cmd)} ${gray("--help")} ${dim("·")} ${dim(p.label)}`,
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
console.log(div2);
|
|
467
|
+
console.log("");
|
|
468
|
+
|
|
469
|
+
showCursor();
|
|
470
|
+
process.exit(0);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
main().catch((err) => {
|
|
476
|
+
showCursor();
|
|
477
|
+
console.error(red("\n ✗ " + err.message + "\n"));
|
|
478
|
+
process.exit(1);
|
|
479
|
+
});
|