@storifycli/cli 0.1.1 → 0.2.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/cli.js +488 -215
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -301,8 +301,86 @@ async function runInteractiveLoginFlow(options) {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
// src/lib/ui.ts
|
|
304
|
-
import
|
|
304
|
+
import pc2 from "picocolors";
|
|
305
305
|
import ora from "ora";
|
|
306
|
+
|
|
307
|
+
// src/lib/brand-banner.ts
|
|
308
|
+
import pc from "picocolors";
|
|
309
|
+
var brandName = "Storify";
|
|
310
|
+
var brandTagline = "Theme development toolkit";
|
|
311
|
+
var LOGO_FACE = [
|
|
312
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
313
|
+
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D",
|
|
314
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ",
|
|
315
|
+
" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2588\u2588\u2554\u255D ",
|
|
316
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
317
|
+
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D "
|
|
318
|
+
];
|
|
319
|
+
var LOGO_SHADOW = [" \u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580 \u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580 \u2580\u2580\u2580\u2580\u2580\u2580\u2580 \u2580\u2580\u2580\u2580\u2580\u2580 \u2580\u2580 \u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580 \u2580\u2580 \u2580\u2580"];
|
|
320
|
+
function supportsColor() {
|
|
321
|
+
return pc.isColorSupported;
|
|
322
|
+
}
|
|
323
|
+
function colorFace(line) {
|
|
324
|
+
if (!supportsColor()) return line;
|
|
325
|
+
return pc.bold(pc.cyan(line));
|
|
326
|
+
}
|
|
327
|
+
function colorShadow(line) {
|
|
328
|
+
if (!supportsColor()) return line;
|
|
329
|
+
return pc.dim(pc.blue(line));
|
|
330
|
+
}
|
|
331
|
+
function colorAccent(line) {
|
|
332
|
+
if (!supportsColor()) return line;
|
|
333
|
+
return pc.bold(pc.white(line));
|
|
334
|
+
}
|
|
335
|
+
function paintMuted(text3) {
|
|
336
|
+
if (!supportsColor()) return text3;
|
|
337
|
+
return pc.dim(text3);
|
|
338
|
+
}
|
|
339
|
+
function paintAccent(text3) {
|
|
340
|
+
if (!supportsColor()) return text3;
|
|
341
|
+
return pc.magenta(text3);
|
|
342
|
+
}
|
|
343
|
+
function paintBrand(text3) {
|
|
344
|
+
if (!supportsColor()) return text3;
|
|
345
|
+
return pc.bold(pc.cyan(text3));
|
|
346
|
+
}
|
|
347
|
+
function centerText(text3, width) {
|
|
348
|
+
const trimmed = text3.length > width ? text3.slice(0, width) : text3;
|
|
349
|
+
const pad = Math.max(0, Math.floor((width - trimmed.length) / 2));
|
|
350
|
+
return `${" ".repeat(pad)}${trimmed}${" ".repeat(Math.max(0, width - pad - trimmed.length))}`;
|
|
351
|
+
}
|
|
352
|
+
function printPlatformBanner(subtitle) {
|
|
353
|
+
const logoWidth = LOGO_FACE[0].length;
|
|
354
|
+
const rule = "\u2550".repeat(logoWidth);
|
|
355
|
+
const tagline = subtitle || "Theme live preview";
|
|
356
|
+
console.log("");
|
|
357
|
+
console.log(colorShadow(` ${rule}`));
|
|
358
|
+
for (const line of LOGO_SHADOW) {
|
|
359
|
+
console.log(colorShadow(` ${line}`));
|
|
360
|
+
}
|
|
361
|
+
for (const line of LOGO_FACE) {
|
|
362
|
+
console.log(colorFace(` ${line}`));
|
|
363
|
+
}
|
|
364
|
+
console.log(colorShadow(` ${rule}`));
|
|
365
|
+
console.log("");
|
|
366
|
+
console.log(paintAccent(` ${centerText(tagline, logoWidth)}`));
|
|
367
|
+
console.log(paintMuted(` ${centerText(brandTagline, logoWidth)}`));
|
|
368
|
+
console.log("");
|
|
369
|
+
console.log(colorAccent(` ${"\u2593".repeat(logoWidth)}`));
|
|
370
|
+
console.log("");
|
|
371
|
+
}
|
|
372
|
+
function printBannerWithLogo(version2) {
|
|
373
|
+
console.log("");
|
|
374
|
+
for (const line of LOGO_FACE) {
|
|
375
|
+
console.log(colorFace(` ${line}`));
|
|
376
|
+
}
|
|
377
|
+
console.log("");
|
|
378
|
+
console.log(paintBrand(` ${centerText(`${brandName} CLI v${version2}`, LOGO_FACE[0].length)}`));
|
|
379
|
+
console.log(paintMuted(` ${centerText(brandTagline, LOGO_FACE[0].length)}`));
|
|
380
|
+
console.log("");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/lib/ui.ts
|
|
306
384
|
var brand = {
|
|
307
385
|
name: "Storify",
|
|
308
386
|
cli: "storify",
|
|
@@ -311,26 +389,26 @@ var brand = {
|
|
|
311
389
|
function isInteractive() {
|
|
312
390
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
313
391
|
}
|
|
314
|
-
function
|
|
315
|
-
return
|
|
392
|
+
function supportsColor2() {
|
|
393
|
+
return pc2.isColorSupported;
|
|
316
394
|
}
|
|
317
395
|
function paint(text3, tone) {
|
|
318
|
-
if (!
|
|
396
|
+
if (!supportsColor2()) return text3;
|
|
319
397
|
switch (tone) {
|
|
320
398
|
case "brand":
|
|
321
|
-
return
|
|
399
|
+
return pc2.bold(pc2.cyan(text3));
|
|
322
400
|
case "muted":
|
|
323
|
-
return
|
|
401
|
+
return pc2.dim(text3);
|
|
324
402
|
case "success":
|
|
325
|
-
return
|
|
403
|
+
return pc2.green(text3);
|
|
326
404
|
case "warn":
|
|
327
|
-
return
|
|
405
|
+
return pc2.yellow(text3);
|
|
328
406
|
case "error":
|
|
329
|
-
return
|
|
407
|
+
return pc2.red(text3);
|
|
330
408
|
case "info":
|
|
331
|
-
return
|
|
409
|
+
return pc2.blue(text3);
|
|
332
410
|
case "accent":
|
|
333
|
-
return
|
|
411
|
+
return pc2.magenta(text3);
|
|
334
412
|
default:
|
|
335
413
|
return text3;
|
|
336
414
|
}
|
|
@@ -341,12 +419,7 @@ function formatBytes(bytes) {
|
|
|
341
419
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
342
420
|
}
|
|
343
421
|
function printBanner(version2) {
|
|
344
|
-
|
|
345
|
-
console.log("");
|
|
346
|
-
console.log(paint(` ${brand.name} CLI`, "brand") + paint(` v${version2}`, "muted"));
|
|
347
|
-
console.log(paint(` ${brand.tagline}`, "muted"));
|
|
348
|
-
console.log(paint(` ${line}`, "muted"));
|
|
349
|
-
console.log("");
|
|
422
|
+
printBannerWithLogo(version2);
|
|
350
423
|
}
|
|
351
424
|
function printHeading(title, subtitle) {
|
|
352
425
|
console.log("");
|
|
@@ -377,16 +450,6 @@ function printHint(message) {
|
|
|
377
450
|
function printCommandExample(label, command) {
|
|
378
451
|
console.log(` ${paint(label.padEnd(16), "muted")}${paint(command, "accent")}`);
|
|
379
452
|
}
|
|
380
|
-
function printPreviewPanel(title, rows) {
|
|
381
|
-
console.log("");
|
|
382
|
-
console.log(paint(` ${title}`, "brand"));
|
|
383
|
-
printDivider();
|
|
384
|
-
const labelWidth = Math.max(16, ...rows.map((row) => row.label.length + 2));
|
|
385
|
-
for (const row of rows) {
|
|
386
|
-
printStep(row.label, row.value, labelWidth);
|
|
387
|
-
}
|
|
388
|
-
console.log("");
|
|
389
|
-
}
|
|
390
453
|
function printQuickStart() {
|
|
391
454
|
printHeading("Quick start");
|
|
392
455
|
printCommandExample("Live storefront preview", "storify theme dev");
|
|
@@ -407,14 +470,14 @@ function createSpinner(text3) {
|
|
|
407
470
|
});
|
|
408
471
|
}
|
|
409
472
|
async function withSpinner(text3, task) {
|
|
410
|
-
const
|
|
411
|
-
|
|
473
|
+
const spinner3 = createSpinner(text3);
|
|
474
|
+
spinner3.start();
|
|
412
475
|
try {
|
|
413
476
|
const result = await task();
|
|
414
|
-
|
|
477
|
+
spinner3.stop();
|
|
415
478
|
return result;
|
|
416
479
|
} catch (error) {
|
|
417
|
-
|
|
480
|
+
spinner3.fail(text3);
|
|
418
481
|
throw error;
|
|
419
482
|
}
|
|
420
483
|
}
|
|
@@ -843,9 +906,106 @@ async function runBuildCommand(options) {
|
|
|
843
906
|
import path6 from "path";
|
|
844
907
|
import { execa as execa3 } from "execa";
|
|
845
908
|
|
|
909
|
+
// src/lib/clack-ui.ts
|
|
910
|
+
import * as p2 from "@clack/prompts";
|
|
911
|
+
import pc3 from "picocolors";
|
|
912
|
+
async function runTask(startMessage, doneMessage, task) {
|
|
913
|
+
const spinner3 = p2.spinner();
|
|
914
|
+
spinner3.start(startMessage);
|
|
915
|
+
try {
|
|
916
|
+
const result = await task();
|
|
917
|
+
spinner3.stop(pc3.green("\u2713") + ` ${doneMessage}`);
|
|
918
|
+
return result;
|
|
919
|
+
} catch (error) {
|
|
920
|
+
spinner3.stop(pc3.red("\u2717") + ` ${startMessage}`);
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/lib/dev-dashboard.ts
|
|
926
|
+
import pc4 from "picocolors";
|
|
927
|
+
|
|
928
|
+
// src/lib/terminal-screen.ts
|
|
929
|
+
function clearScreen() {
|
|
930
|
+
if (!isInteractive()) return;
|
|
931
|
+
process.stdout.write("\x1Bc");
|
|
932
|
+
}
|
|
933
|
+
function hideCursor() {
|
|
934
|
+
if (!isInteractive()) return;
|
|
935
|
+
process.stdout.write("\x1B[?25l");
|
|
936
|
+
}
|
|
937
|
+
function showCursor() {
|
|
938
|
+
if (!isInteractive()) return;
|
|
939
|
+
process.stdout.write("\x1B[?25h");
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/lib/dev-dashboard.ts
|
|
943
|
+
function truncateMiddle(value, max = 64) {
|
|
944
|
+
if (value.length <= max) return value;
|
|
945
|
+
const head = Math.ceil((max - 1) / 2);
|
|
946
|
+
const tail = Math.floor((max - 1) / 2);
|
|
947
|
+
return `${value.slice(0, head)}\u2026${value.slice(-tail)}`;
|
|
948
|
+
}
|
|
949
|
+
function renderUrlPanel(urls) {
|
|
950
|
+
const w = 62;
|
|
951
|
+
console.log(paint(` \u256D${"\u2500".repeat(w)}\u256E`, "brand"));
|
|
952
|
+
console.log(
|
|
953
|
+
paint(` \u2502${" ".repeat(Math.floor((w - 13) / 2))}Preview URLs${" ".repeat(Math.ceil((w - 13) / 2))}\u2502`, "brand")
|
|
954
|
+
);
|
|
955
|
+
console.log(paint(` \u251C${"\u2500".repeat(w)}\u2524`, "brand"));
|
|
956
|
+
for (const row of urls) {
|
|
957
|
+
const label = row.label.padEnd(14);
|
|
958
|
+
const value = truncateMiddle(row.value, w - 20);
|
|
959
|
+
const gap = Math.max(1, w - label.length - value.length - 1);
|
|
960
|
+
console.log(` \u2502 ${paint(label, "muted")}${value}${" ".repeat(gap)}\u2502`);
|
|
961
|
+
}
|
|
962
|
+
console.log(paint(` \u2570${"\u2500".repeat(w)}\u256F`, "brand"));
|
|
963
|
+
}
|
|
964
|
+
function renderControls(interactiveShell) {
|
|
965
|
+
const w = 62;
|
|
966
|
+
console.log("");
|
|
967
|
+
console.log(paint(` \u256D${"\u2500".repeat(w)}\u256E`, "brand"));
|
|
968
|
+
console.log(
|
|
969
|
+
paint(` \u2502${" ".repeat(Math.floor((w - 8) / 2))}Controls${" ".repeat(Math.ceil((w - 8) / 2))}\u2502`, "brand")
|
|
970
|
+
);
|
|
971
|
+
console.log(paint(` \u251C${"\u2500".repeat(w)}\u2524`, "brand"));
|
|
972
|
+
console.log(paint(` \u2502 ${paint("Enter / Q".padEnd(14), "muted")}Stop preview \xB7 disable dev link`, "muted"));
|
|
973
|
+
console.log(
|
|
974
|
+
paint(
|
|
975
|
+
` \u2502 ${paint("Ctrl+C".padEnd(14), "muted")}${interactiveShell ? "Stop \xB7 return to main menu" : "Stop preview \xB7 exit"}`,
|
|
976
|
+
"muted"
|
|
977
|
+
)
|
|
978
|
+
);
|
|
979
|
+
console.log(paint(` \u2502 ${paint("Save files".padEnd(14), "muted")}Vite hot-reloads in the background`, "muted"));
|
|
980
|
+
console.log(paint(` \u2570${"\u2500".repeat(w)}\u256F`, "brand"));
|
|
981
|
+
console.log("");
|
|
982
|
+
}
|
|
983
|
+
function renderDevDashboard(options) {
|
|
984
|
+
clearScreen();
|
|
985
|
+
printPlatformBanner(options.subtitle);
|
|
986
|
+
if (options.storeLabel) {
|
|
987
|
+
console.log(
|
|
988
|
+
paint(` ${paint("Store", "muted")} ${options.storeLabel}`, "info")
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
console.log(paint(` ${paint("Theme", "muted")} ${truncateMiddle(options.themeRoot, 70)}`, "info"));
|
|
992
|
+
console.log("");
|
|
993
|
+
for (const step of options.steps) {
|
|
994
|
+
const mark = supportsColor2() ? pc4.green("\u2713") : "\u2713";
|
|
995
|
+
console.log(` ${mark} ${paint(step.label.padEnd(16), "muted")}${step.value}`);
|
|
996
|
+
}
|
|
997
|
+
console.log("");
|
|
998
|
+
renderUrlPanel(options.urls);
|
|
999
|
+
if (options.note) {
|
|
1000
|
+
console.log(paint(` ${options.note}`, "muted"));
|
|
1001
|
+
}
|
|
1002
|
+
renderControls(options.interactiveShell);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
846
1005
|
// src/lib/dev-link.ts
|
|
847
1006
|
async function updateDevLink(auth, payload) {
|
|
848
|
-
const
|
|
1007
|
+
const url = `${auth.apiUrl}/theme/dev-link`;
|
|
1008
|
+
const init = {
|
|
849
1009
|
method: "PATCH",
|
|
850
1010
|
headers: {
|
|
851
1011
|
"Content-Type": "application/json",
|
|
@@ -853,18 +1013,72 @@ async function updateDevLink(auth, payload) {
|
|
|
853
1013
|
"X-Store-Id": auth.storeId
|
|
854
1014
|
},
|
|
855
1015
|
body: JSON.stringify(payload)
|
|
856
|
-
});
|
|
857
|
-
const body = await response.json().catch(() => ({}));
|
|
858
|
-
if (!response.ok) {
|
|
859
|
-
const message = (body && typeof body === "object" && "error" in body ? String(body.error) : "") || `Failed to update dev link (${response.status})`;
|
|
860
|
-
throw new Error(message);
|
|
861
|
-
}
|
|
862
|
-
return {
|
|
863
|
-
devThemeEnabled: body.devThemeEnabled === true,
|
|
864
|
-
devThemeBaseUrl: body.devThemeBaseUrl == null || body.devThemeBaseUrl === "" ? null : String(body.devThemeBaseUrl).trim(),
|
|
865
|
-
storefrontPreviewUrl: typeof body.storefrontPreviewUrl === "string" ? body.storefrontPreviewUrl : void 0,
|
|
866
|
-
devSessionId: typeof body.devSessionId === "string" ? body.devSessionId : void 0
|
|
867
1016
|
};
|
|
1017
|
+
const retryDelaysMs = [0, 2e3, 5e3];
|
|
1018
|
+
let lastError = null;
|
|
1019
|
+
for (const delayMs of retryDelaysMs) {
|
|
1020
|
+
if (delayMs > 0) {
|
|
1021
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1022
|
+
}
|
|
1023
|
+
const response = await fetch(url, init);
|
|
1024
|
+
const body = await response.json().catch(() => ({}));
|
|
1025
|
+
if (response.status === 429) {
|
|
1026
|
+
lastError = new Error(
|
|
1027
|
+
(body && typeof body === "object" && "error" in body ? String(body.error) : "") || "Too many requests, please slow down"
|
|
1028
|
+
);
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
if (!response.ok) {
|
|
1032
|
+
const message = (body && typeof body === "object" && "error" in body ? String(body.error) : "") || `Failed to update dev link (${response.status})`;
|
|
1033
|
+
throw new Error(message);
|
|
1034
|
+
}
|
|
1035
|
+
return {
|
|
1036
|
+
devThemeEnabled: body.devThemeEnabled === true,
|
|
1037
|
+
devThemeBaseUrl: body.devThemeBaseUrl == null || body.devThemeBaseUrl === "" ? null : String(body.devThemeBaseUrl).trim(),
|
|
1038
|
+
storefrontPreviewUrl: typeof body.storefrontPreviewUrl === "string" ? body.storefrontPreviewUrl : void 0,
|
|
1039
|
+
devSessionId: typeof body.devSessionId === "string" ? body.devSessionId : void 0
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
throw lastError ?? new Error("Failed to update dev link (rate limited)");
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/lib/dev-session-wait.ts
|
|
1046
|
+
import readline from "readline";
|
|
1047
|
+
async function waitForDevSessionEnd(viteProcess, options) {
|
|
1048
|
+
if (!isInteractive()) {
|
|
1049
|
+
await viteProcess;
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
hideCursor();
|
|
1053
|
+
await new Promise((resolve, reject) => {
|
|
1054
|
+
let settled = false;
|
|
1055
|
+
const finish = (error) => {
|
|
1056
|
+
if (settled) return;
|
|
1057
|
+
settled = true;
|
|
1058
|
+
showCursor();
|
|
1059
|
+
rl.close();
|
|
1060
|
+
if (error) reject(error);
|
|
1061
|
+
else resolve();
|
|
1062
|
+
};
|
|
1063
|
+
viteProcess.then(() => finish()).catch((error) => finish(error));
|
|
1064
|
+
const rl = readline.createInterface({
|
|
1065
|
+
input: process.stdin,
|
|
1066
|
+
output: process.stdout,
|
|
1067
|
+
terminal: true
|
|
1068
|
+
});
|
|
1069
|
+
const stopSession = () => {
|
|
1070
|
+
void options.stop().then(
|
|
1071
|
+
() => finish(),
|
|
1072
|
+
(error) => finish(error)
|
|
1073
|
+
);
|
|
1074
|
+
};
|
|
1075
|
+
rl.on("line", (line) => {
|
|
1076
|
+
const key = line.trim().toLowerCase();
|
|
1077
|
+
if (key === "" || key === "q" || key === "quit" || key === "stop" || key === "s") {
|
|
1078
|
+
stopSession();
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
868
1082
|
}
|
|
869
1083
|
|
|
870
1084
|
// src/lib/port.ts
|
|
@@ -900,7 +1114,7 @@ function buildPreviewUrls(auth, devBaseUrl, storefrontUrlOverride) {
|
|
|
900
1114
|
}
|
|
901
1115
|
|
|
902
1116
|
// src/lib/session.ts
|
|
903
|
-
import * as
|
|
1117
|
+
import * as p3 from "@clack/prompts";
|
|
904
1118
|
async function ensureAuthenticatedSession(options = {}) {
|
|
905
1119
|
const stored = await readStoredConfig();
|
|
906
1120
|
const hasCredentials = Boolean(stored.token?.trim() && stored.storeId?.trim());
|
|
@@ -927,7 +1141,7 @@ async function ensureAuthenticatedSession(options = {}) {
|
|
|
927
1141
|
if (!options.token && !options.forceLogin) {
|
|
928
1142
|
const valid = await verifyAuthToken(apiUrl, token);
|
|
929
1143
|
if (!valid && isInteractive()) {
|
|
930
|
-
|
|
1144
|
+
p3.log.warn("Session expired. Sign in again.");
|
|
931
1145
|
const session = await runInteractiveLoginFlow({
|
|
932
1146
|
apiUrl: options.api,
|
|
933
1147
|
currentStoreId: options.storeId || stored.storeId
|
|
@@ -1001,10 +1215,14 @@ async function waitForDevServer(baseUrl, timeoutMs = 45e3) {
|
|
|
1001
1215
|
|
|
1002
1216
|
// src/lib/tunnel.ts
|
|
1003
1217
|
async function startCloudflaredTunnel(localUrl) {
|
|
1004
|
-
const child = execa2(
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1218
|
+
const child = execa2(
|
|
1219
|
+
"cloudflared",
|
|
1220
|
+
["tunnel", "--url", localUrl, "--no-autoupdate", "--loglevel", "error"],
|
|
1221
|
+
{
|
|
1222
|
+
all: true,
|
|
1223
|
+
reject: false
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1008
1226
|
const url = await new Promise((resolve, reject) => {
|
|
1009
1227
|
const timeout = setTimeout(() => {
|
|
1010
1228
|
reject(new Error("Timed out while waiting for cloudflared public URL."));
|
|
@@ -1057,7 +1275,6 @@ async function runDevCommand(options) {
|
|
|
1057
1275
|
const preferredPort = toPort(options.port);
|
|
1058
1276
|
const shouldLinkStore = options.linkStore !== false;
|
|
1059
1277
|
const shouldUnlinkOnExit = options.unlinkOnExit !== false;
|
|
1060
|
-
printHeading("Theme preview", themeRoot);
|
|
1061
1278
|
const session = await ensureAuthenticatedSession({
|
|
1062
1279
|
api: options.api,
|
|
1063
1280
|
storeId: options.storeId,
|
|
@@ -1073,33 +1290,36 @@ async function runDevCommand(options) {
|
|
|
1073
1290
|
if (useLocalDev) {
|
|
1074
1291
|
auth.storefrontUrl = localStorefrontBaseUrl(auth.storefrontUrl);
|
|
1075
1292
|
}
|
|
1293
|
+
const subtitle = options.remote ? "Remote preview \xB7 production storefront" : useLocalDev ? "Local preview \xB7 localhost storefront" : "Theme live preview";
|
|
1076
1294
|
const port = await findAvailablePort(preferredPort);
|
|
1077
1295
|
const localUrl = `http://localhost:${port}`;
|
|
1078
1296
|
const shouldUseTunnel = !useLocalDev && options.tunnel !== false;
|
|
1079
|
-
|
|
1080
|
-
printWarn(`Port ${preferredPort} was busy. Using ${port} instead.`);
|
|
1081
|
-
}
|
|
1297
|
+
const portNote = port !== preferredPort ? ` (port ${preferredPort} was busy)` : "";
|
|
1082
1298
|
let tunnelProcess = null;
|
|
1083
1299
|
let linkedDevUrl = null;
|
|
1084
1300
|
let liveStorefrontUrl = null;
|
|
1301
|
+
let viteProcess = null;
|
|
1302
|
+
let viteStderr = "";
|
|
1085
1303
|
const viteBin = path6.join(themeRoot, "node_modules", "vite", "bin", "vite.js");
|
|
1086
|
-
const viteArgs = [
|
|
1304
|
+
const viteArgs = [
|
|
1305
|
+
viteBin,
|
|
1306
|
+
"--host",
|
|
1307
|
+
"0.0.0.0",
|
|
1308
|
+
"--port",
|
|
1309
|
+
String(port),
|
|
1310
|
+
"--strictPort",
|
|
1311
|
+
"--logLevel",
|
|
1312
|
+
"error"
|
|
1313
|
+
];
|
|
1087
1314
|
const viteEnv = { ...process.env };
|
|
1088
1315
|
viteEnv.VITE_DEV_STORE_ID = auth.storeId;
|
|
1089
|
-
let viteProcess = null;
|
|
1090
1316
|
const teardown = async () => {
|
|
1091
|
-
if (tunnelProcess)
|
|
1092
|
-
|
|
1093
|
-
}
|
|
1094
|
-
if (viteProcess) {
|
|
1095
|
-
viteProcess.kill("SIGTERM");
|
|
1096
|
-
}
|
|
1317
|
+
if (tunnelProcess) tunnelProcess.kill("SIGTERM");
|
|
1318
|
+
if (viteProcess) viteProcess.kill("SIGTERM");
|
|
1097
1319
|
if (shouldLinkStore && shouldUnlinkOnExit) {
|
|
1098
1320
|
try {
|
|
1099
1321
|
await updateDevLink(auth, { devThemeEnabled: false, devThemeBaseUrl: null });
|
|
1100
|
-
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
printWarn(`Failed to disable dev link: ${error.message}`);
|
|
1322
|
+
} catch {
|
|
1103
1323
|
}
|
|
1104
1324
|
}
|
|
1105
1325
|
};
|
|
@@ -1109,88 +1329,103 @@ async function runDevCommand(options) {
|
|
|
1109
1329
|
tearingDown = true;
|
|
1110
1330
|
await teardown();
|
|
1111
1331
|
};
|
|
1112
|
-
|
|
1332
|
+
const onSigint = async () => {
|
|
1113
1333
|
await handleExit();
|
|
1114
|
-
process.exit(130);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1334
|
+
if (!options.interactiveShell) process.exit(130);
|
|
1335
|
+
};
|
|
1336
|
+
const onSigterm = async () => {
|
|
1117
1337
|
await handleExit();
|
|
1118
|
-
process.exit(143);
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
});
|
|
1128
|
-
viteProcess.stdout?.pipe(process.stdout);
|
|
1129
|
-
viteProcess.stderr?.pipe(process.stderr);
|
|
1338
|
+
if (!options.interactiveShell) process.exit(143);
|
|
1339
|
+
};
|
|
1340
|
+
process.on("SIGINT", onSigint);
|
|
1341
|
+
process.on("SIGTERM", onSigterm);
|
|
1342
|
+
const removeSignalHandlers = () => {
|
|
1343
|
+
process.off("SIGINT", onSigint);
|
|
1344
|
+
process.off("SIGTERM", onSigterm);
|
|
1345
|
+
};
|
|
1346
|
+
const dashboardSteps = [];
|
|
1130
1347
|
try {
|
|
1131
|
-
await
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1348
|
+
await runTask(`Starting Vite${portNote}`, `Vite on :${port}`, async () => {
|
|
1349
|
+
viteProcess = execa3("node", viteArgs, {
|
|
1350
|
+
cwd: themeRoot,
|
|
1351
|
+
env: viteEnv,
|
|
1352
|
+
stdout: "ignore",
|
|
1353
|
+
stderr: "pipe"
|
|
1354
|
+
});
|
|
1355
|
+
viteProcess.stderr?.on("data", (chunk) => {
|
|
1356
|
+
viteStderr += String(chunk);
|
|
1357
|
+
});
|
|
1358
|
+
await waitForDevServer(localUrl);
|
|
1359
|
+
});
|
|
1360
|
+
dashboardSteps.push({ label: "Vite dev server", value: localUrl });
|
|
1361
|
+
if (shouldLinkStore) {
|
|
1362
|
+
if (useLocalDev) {
|
|
1363
|
+
linkedDevUrl = localUrl;
|
|
1364
|
+
} else if (options.publicUrl) {
|
|
1365
|
+
linkedDevUrl = options.publicUrl.replace(/\/+$/, "");
|
|
1366
|
+
} else if (shouldUseTunnel) {
|
|
1367
|
+
try {
|
|
1368
|
+
const tunnel = await runTask(
|
|
1369
|
+
"Creating secure tunnel",
|
|
1370
|
+
"Public tunnel ready",
|
|
1371
|
+
() => startCloudflaredTunnel(localUrl)
|
|
1372
|
+
);
|
|
1373
|
+
linkedDevUrl = tunnel.url.replace(/\/+$/, "");
|
|
1374
|
+
tunnelProcess = tunnel.process;
|
|
1375
|
+
dashboardSteps.push({ label: "Public tunnel", value: linkedDevUrl });
|
|
1376
|
+
} catch {
|
|
1377
|
+
linkedDevUrl = localUrl;
|
|
1378
|
+
dashboardSteps.push({ label: "Public tunnel", value: "Unavailable \u2014 using localhost" });
|
|
1379
|
+
}
|
|
1380
|
+
} else {
|
|
1154
1381
|
linkedDevUrl = localUrl;
|
|
1155
|
-
printWarn("Storefront preview needs a public URL. Use --local or install cloudflared.");
|
|
1156
1382
|
}
|
|
1383
|
+
const devLinkResult = await runTask(
|
|
1384
|
+
"Linking store preview",
|
|
1385
|
+
"Store preview linked",
|
|
1386
|
+
() => updateDevLink(auth, { devThemeEnabled: true, devThemeBaseUrl: linkedDevUrl })
|
|
1387
|
+
);
|
|
1388
|
+
liveStorefrontUrl = (useLocalDev ? formatLivePreviewUrl(auth, devLinkResult.devSessionId, session.store) : devLinkResult.storefrontPreviewUrl) || formatLivePreviewUrl(auth, devLinkResult.devSessionId, session.store);
|
|
1389
|
+
dashboardSteps.push({
|
|
1390
|
+
label: "Storefront link",
|
|
1391
|
+
value: session.store ? displayStoreName(session.store) : auth.storeId
|
|
1392
|
+
});
|
|
1157
1393
|
} else {
|
|
1158
1394
|
linkedDevUrl = localUrl;
|
|
1395
|
+
dashboardSteps.push({ label: "Local only", value: "Store link skipped" });
|
|
1159
1396
|
}
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1397
|
+
const urls = shouldLinkStore && linkedDevUrl && liveStorefrontUrl ? buildPreviewUrls(auth, linkedDevUrl, liveStorefrontUrl) : null;
|
|
1398
|
+
renderDevDashboard({
|
|
1399
|
+
subtitle,
|
|
1400
|
+
themeRoot,
|
|
1401
|
+
storeLabel: session.store ? displayStoreName(session.store) : auth.storeId,
|
|
1402
|
+
steps: dashboardSteps,
|
|
1403
|
+
urls: urls ? [
|
|
1404
|
+
{ label: "Storefront", value: urls.storefrontUrl },
|
|
1405
|
+
{ label: "Theme (Vite)", value: linkedDevUrl },
|
|
1406
|
+
{ label: "Theme direct", value: urls.themeDirectUrl },
|
|
1407
|
+
{ label: "Admin editor", value: urls.adminEditorUrl }
|
|
1408
|
+
] : [{ label: "Vite", value: localUrl }],
|
|
1409
|
+
interactiveShell: options.interactiveShell,
|
|
1410
|
+
note: useLocalDev ? "Ensure the storefront is running on localhost:3004" : "Edit theme files \u2014 changes hot-reload on the storefront"
|
|
1411
|
+
});
|
|
1412
|
+
const openTarget = options.openStorefront && liveStorefrontUrl ? liveStorefrontUrl : options.open ? localUrl : useLocalDev && shouldLinkStore && liveStorefrontUrl ? liveStorefrontUrl : null;
|
|
1413
|
+
if (openTarget) {
|
|
1414
|
+
await openInBrowser(openTarget);
|
|
1176
1415
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
}
|
|
1188
|
-
printSuccess("Waiting for file changes...");
|
|
1189
|
-
console.log("");
|
|
1190
|
-
try {
|
|
1191
|
-
await viteProcess;
|
|
1416
|
+
if (!viteProcess) throw new Error("Vite process failed to start.");
|
|
1417
|
+
await waitForDevSessionEnd(viteProcess, {
|
|
1418
|
+
interactiveShell: options.interactiveShell,
|
|
1419
|
+
stop: handleExit
|
|
1420
|
+
});
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
if (viteStderr.trim()) {
|
|
1423
|
+
console.error("\n" + viteStderr.trim() + "\n");
|
|
1424
|
+
}
|
|
1425
|
+
throw error;
|
|
1192
1426
|
} finally {
|
|
1193
|
-
|
|
1427
|
+
removeSignalHandlers();
|
|
1428
|
+
if (!tearingDown) await handleExit();
|
|
1194
1429
|
}
|
|
1195
1430
|
}
|
|
1196
1431
|
|
|
@@ -1567,40 +1802,40 @@ async function runValidateCommand(options) {
|
|
|
1567
1802
|
const manifestPath = path11.join(themeRoot, "theme-manifest.json");
|
|
1568
1803
|
const manifestRaw = await fs11.readFile(manifestPath, "utf8");
|
|
1569
1804
|
const manifest = JSON.parse(manifestRaw);
|
|
1570
|
-
let
|
|
1571
|
-
|
|
1805
|
+
let spinner3 = createSpinner("Checking manifest DSL");
|
|
1806
|
+
spinner3.start();
|
|
1572
1807
|
const dslValidation = validateThemeManifestDSL(manifest);
|
|
1573
1808
|
if (!dslValidation.ok) {
|
|
1574
|
-
|
|
1809
|
+
spinner3.fail("Manifest DSL validation failed");
|
|
1575
1810
|
throw new Error(dslValidation.error);
|
|
1576
1811
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1812
|
+
spinner3.succeed("Manifest DSL is valid");
|
|
1813
|
+
spinner3 = createSpinner("Checking page section references");
|
|
1814
|
+
spinner3.start();
|
|
1580
1815
|
const refValidation = validateThemeManifestDSLReferences(dslValidation.manifest);
|
|
1581
1816
|
if (!refValidation.ok) {
|
|
1582
|
-
|
|
1817
|
+
spinner3.fail("Manifest references validation failed");
|
|
1583
1818
|
throw new Error(refValidation.error);
|
|
1584
1819
|
}
|
|
1585
|
-
|
|
1820
|
+
spinner3.succeed("Page layout references are valid");
|
|
1586
1821
|
const sectionRendererPath = path11.join(themeRoot, "src", "SectionRenderer.tsx");
|
|
1587
1822
|
if (await fileExists3(sectionRendererPath)) {
|
|
1588
|
-
|
|
1589
|
-
|
|
1823
|
+
spinner3 = createSpinner("Matching SECTION_MAP components");
|
|
1824
|
+
spinner3.start();
|
|
1590
1825
|
const sectionMapInfo = await validateManifestSectionMap(themeRoot);
|
|
1591
|
-
|
|
1826
|
+
spinner3.succeed(`SECTION_MAP matched (${sectionMapInfo.sections} sections, ${sectionMapInfo.mapKeys} keys)`);
|
|
1592
1827
|
} else {
|
|
1593
1828
|
printWarn("Skipped SECTION_MAP check (src/SectionRenderer.tsx not found).");
|
|
1594
1829
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1830
|
+
spinner3 = createSpinner("Validating config/pages defaults");
|
|
1831
|
+
spinner3.start();
|
|
1597
1832
|
const dryRun = await buildThemeManifest({
|
|
1598
1833
|
themeRoot,
|
|
1599
1834
|
writeToDist: false,
|
|
1600
1835
|
syncEntryFromDist: false,
|
|
1601
1836
|
quiet: true
|
|
1602
1837
|
});
|
|
1603
|
-
|
|
1838
|
+
spinner3.succeed("Page defaults are consistent");
|
|
1604
1839
|
if (!dslValidation.manifest.entry || !dslValidation.manifest.entry.trim()) {
|
|
1605
1840
|
throw new Error("Manifest entry is required.");
|
|
1606
1841
|
}
|
|
@@ -1623,79 +1858,117 @@ async function runValidateCommand(options) {
|
|
|
1623
1858
|
}
|
|
1624
1859
|
|
|
1625
1860
|
// src/lib/interactive.ts
|
|
1626
|
-
import * as
|
|
1627
|
-
import
|
|
1861
|
+
import * as p4 from "@clack/prompts";
|
|
1862
|
+
import pc5 from "picocolors";
|
|
1863
|
+
function showMenuShell() {
|
|
1864
|
+
clearScreen();
|
|
1865
|
+
printPlatformBanner("Theme development toolkit");
|
|
1866
|
+
}
|
|
1867
|
+
var MENU_OPTIONS = [
|
|
1868
|
+
{ value: "preview", label: "Start live theme preview", hint: "Local or remote storefront" },
|
|
1869
|
+
{ value: "validate", label: "Validate theme", hint: "Manifest + sections" },
|
|
1870
|
+
{ value: "build", label: "Build theme", hint: "dist/ output" },
|
|
1871
|
+
{ value: "pack", label: "Pack zip", hint: "Upload-ready archive" },
|
|
1872
|
+
{ value: "upload", label: "Upload theme", hint: "Send zip to Storify" },
|
|
1873
|
+
{ value: "new", label: "Create new theme", hint: "From starter template" },
|
|
1874
|
+
{ value: "login", label: "Sign in / switch store", hint: "Save credentials" },
|
|
1875
|
+
{ value: "status", label: "Check setup", hint: "Config + theme folder" },
|
|
1876
|
+
{ value: "help", label: "Show quick start", hint: "Common commands" },
|
|
1877
|
+
{ value: "exit", label: "Exit", hint: "Leave the CLI" }
|
|
1878
|
+
];
|
|
1879
|
+
async function runMenuAction(action) {
|
|
1880
|
+
switch (action) {
|
|
1881
|
+
case "preview": {
|
|
1882
|
+
const mode = await p4.select({
|
|
1883
|
+
message: "Preview mode",
|
|
1884
|
+
options: [
|
|
1885
|
+
{ value: "local", label: "Local", hint: "localhost storefront + Vite" },
|
|
1886
|
+
{ value: "remote", label: "Remote", hint: "Production storefront + cloudflared tunnel" }
|
|
1887
|
+
]
|
|
1888
|
+
});
|
|
1889
|
+
if (p4.isCancel(mode)) {
|
|
1890
|
+
p4.cancel("Preview cancelled.");
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
await runDevCommand({
|
|
1894
|
+
linkStore: true,
|
|
1895
|
+
openStorefront: true,
|
|
1896
|
+
local: mode === "local",
|
|
1897
|
+
remote: mode === "remote",
|
|
1898
|
+
interactiveShell: true
|
|
1899
|
+
});
|
|
1900
|
+
p4.log.success("Preview stopped. Back to menu.");
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
case "validate":
|
|
1904
|
+
await runValidateCommand({});
|
|
1905
|
+
break;
|
|
1906
|
+
case "build":
|
|
1907
|
+
await runBuildCommand({});
|
|
1908
|
+
break;
|
|
1909
|
+
case "pack":
|
|
1910
|
+
await runPackCommand({});
|
|
1911
|
+
break;
|
|
1912
|
+
case "upload":
|
|
1913
|
+
await runUploadCommand({});
|
|
1914
|
+
break;
|
|
1915
|
+
case "new": {
|
|
1916
|
+
const name = await p4.text({
|
|
1917
|
+
message: "Theme folder name",
|
|
1918
|
+
placeholder: "my-theme",
|
|
1919
|
+
validate: (value) => value?.trim() ? void 0 : "Name is required"
|
|
1920
|
+
});
|
|
1921
|
+
if (p4.isCancel(name)) {
|
|
1922
|
+
p4.cancel("Cancelled.");
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
await runNewCommand(String(name).trim(), {});
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
case "login":
|
|
1929
|
+
await runLoginCommand({});
|
|
1930
|
+
break;
|
|
1931
|
+
case "status":
|
|
1932
|
+
await runStatusCommand({});
|
|
1933
|
+
break;
|
|
1934
|
+
case "help":
|
|
1935
|
+
printQuickStart();
|
|
1936
|
+
break;
|
|
1937
|
+
case "exit":
|
|
1938
|
+
break;
|
|
1939
|
+
default:
|
|
1940
|
+
break;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1628
1943
|
async function runInteractiveRoot(version2) {
|
|
1629
1944
|
if (!isInteractive()) {
|
|
1630
1945
|
printBanner(version2);
|
|
1631
1946
|
printQuickStart();
|
|
1632
1947
|
return;
|
|
1633
1948
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
}
|
|
1653
|
-
try {
|
|
1654
|
-
switch (action) {
|
|
1655
|
-
case "preview":
|
|
1656
|
-
await runDevCommand({ linkStore: true, openStorefront: true, local: true });
|
|
1657
|
-
break;
|
|
1658
|
-
case "validate":
|
|
1659
|
-
await runValidateCommand({});
|
|
1660
|
-
break;
|
|
1661
|
-
case "build":
|
|
1662
|
-
await runBuildCommand({});
|
|
1663
|
-
break;
|
|
1664
|
-
case "pack":
|
|
1665
|
-
await runPackCommand({});
|
|
1666
|
-
break;
|
|
1667
|
-
case "upload":
|
|
1668
|
-
await runUploadCommand({});
|
|
1669
|
-
break;
|
|
1670
|
-
case "new": {
|
|
1671
|
-
const name = await p3.text({
|
|
1672
|
-
message: "Theme folder name",
|
|
1673
|
-
placeholder: "my-theme",
|
|
1674
|
-
validate: (value) => value?.trim() ? void 0 : "Name is required"
|
|
1675
|
-
});
|
|
1676
|
-
if (p3.isCancel(name)) {
|
|
1677
|
-
p3.cancel("Cancelled.");
|
|
1678
|
-
process.exit(0);
|
|
1679
|
-
}
|
|
1680
|
-
await runNewCommand(String(name).trim(), {});
|
|
1681
|
-
break;
|
|
1949
|
+
showMenuShell();
|
|
1950
|
+
while (true) {
|
|
1951
|
+
const action = await p4.select({
|
|
1952
|
+
message: "What would you like to do?",
|
|
1953
|
+
options: MENU_OPTIONS
|
|
1954
|
+
});
|
|
1955
|
+
if (p4.isCancel(action)) {
|
|
1956
|
+
p4.outro("Goodbye.");
|
|
1957
|
+
break;
|
|
1958
|
+
}
|
|
1959
|
+
if (action === "exit") {
|
|
1960
|
+
p4.outro("Goodbye.");
|
|
1961
|
+
break;
|
|
1962
|
+
}
|
|
1963
|
+
try {
|
|
1964
|
+
await runMenuAction(action);
|
|
1965
|
+
if (action !== "help") {
|
|
1966
|
+
showMenuShell();
|
|
1682
1967
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
case "status":
|
|
1687
|
-
await runStatusCommand({});
|
|
1688
|
-
break;
|
|
1689
|
-
case "help":
|
|
1690
|
-
printQuickStart();
|
|
1691
|
-
p3.outro("Done.");
|
|
1692
|
-
break;
|
|
1693
|
-
default:
|
|
1694
|
-
break;
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
p4.log.error(error instanceof Error ? error.message : String(error));
|
|
1970
|
+
p4.log.message(pc5.dim("Press \u2191 to pick another command."));
|
|
1695
1971
|
}
|
|
1696
|
-
} catch (error) {
|
|
1697
|
-
p3.log.error(error instanceof Error ? error.message : String(error));
|
|
1698
|
-
process.exit(1);
|
|
1699
1972
|
}
|
|
1700
1973
|
}
|
|
1701
1974
|
|