@gallop.software/studio 2.3.56 → 2.3.58
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/client/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
12
12
|
}
|
|
13
13
|
</style>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-DiOqdQT0.js"></script>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
package/dist/server/index.js
CHANGED
|
@@ -1203,13 +1203,13 @@ async function handleSync(request) {
|
|
|
1203
1203
|
} catch {
|
|
1204
1204
|
}
|
|
1205
1205
|
}
|
|
1206
|
+
await saveMeta(meta);
|
|
1206
1207
|
pushed.push(imageKey);
|
|
1207
1208
|
} catch (error) {
|
|
1208
1209
|
console.error(`Failed to push ${imageKey}:`, error);
|
|
1209
1210
|
errors.push(`Failed to push: ${imageKey}`);
|
|
1210
1211
|
}
|
|
1211
1212
|
}
|
|
1212
|
-
await saveMeta(meta);
|
|
1213
1213
|
for (const folder of sourceFolders) {
|
|
1214
1214
|
await deleteEmptyFolders(folder);
|
|
1215
1215
|
}
|
|
@@ -1306,6 +1306,7 @@ async function handleUnprocessStream(request) {
|
|
|
1306
1306
|
b: entry.b,
|
|
1307
1307
|
...entry.c !== void 0 ? { c: entry.c } : {}
|
|
1308
1308
|
};
|
|
1309
|
+
await saveMeta(meta);
|
|
1309
1310
|
removed.push(imageKey);
|
|
1310
1311
|
sendEvent({
|
|
1311
1312
|
type: "progress",
|
|
@@ -1468,6 +1469,7 @@ async function handleReprocessStream(request) {
|
|
|
1468
1469
|
}
|
|
1469
1470
|
meta[imageKey] = updatedEntry;
|
|
1470
1471
|
}
|
|
1472
|
+
await saveMeta(meta);
|
|
1471
1473
|
processed.push(imageKey);
|
|
1472
1474
|
sendEvent({
|
|
1473
1475
|
type: "progress",
|
|
@@ -1594,6 +1596,7 @@ async function handleDownloadStream(request) {
|
|
|
1594
1596
|
entry.lg = processedEntry.lg;
|
|
1595
1597
|
entry.f = processedEntry.f;
|
|
1596
1598
|
}
|
|
1599
|
+
await saveMeta(meta);
|
|
1597
1600
|
downloaded.push(imageKey);
|
|
1598
1601
|
sendEvent({
|
|
1599
1602
|
type: "progress",
|
|
@@ -1748,6 +1751,7 @@ async function handlePushUpdatesStream(request) {
|
|
|
1748
1751
|
}
|
|
1749
1752
|
await fs6.unlink(localPath);
|
|
1750
1753
|
delete entry.u;
|
|
1754
|
+
await saveMeta(meta);
|
|
1751
1755
|
pushed.push(key);
|
|
1752
1756
|
sendEvent({
|
|
1753
1757
|
type: "progress",
|
|
@@ -2138,7 +2142,7 @@ async function handleRename(request) {
|
|
|
2138
2142
|
return jsonResponse({ error: "An item with this name already exists" }, { status: 400 });
|
|
2139
2143
|
} catch {
|
|
2140
2144
|
}
|
|
2141
|
-
if (isInOurR2 && !hasLocalFile
|
|
2145
|
+
if (isInOurR2 && !hasLocalFile) {
|
|
2142
2146
|
await moveInCdn(oldKey, newKey, hasThumbnails);
|
|
2143
2147
|
delete meta[oldKey];
|
|
2144
2148
|
meta[newKey] = entry;
|
|
@@ -2297,11 +2301,17 @@ async function handleRenameStream(request) {
|
|
|
2297
2301
|
}
|
|
2298
2302
|
sendEvent({ type: "progress", current: 1, total, renamed: 1, message: "Renamed folder" });
|
|
2299
2303
|
let renamed = 1;
|
|
2304
|
+
const handleRenameCancel = async () => {
|
|
2305
|
+
await saveMeta(meta);
|
|
2306
|
+
await deleteEmptyFolders(absoluteOldPath);
|
|
2307
|
+
const oldThumbFolder2 = path7.join(getPublicPath("/images"), oldRelativePath);
|
|
2308
|
+
await deleteEmptyFolders(oldThumbFolder2);
|
|
2309
|
+
sendEvent({ type: "complete", renamed, newPath, cancelled: true });
|
|
2310
|
+
controller.close();
|
|
2311
|
+
};
|
|
2300
2312
|
for (const item of itemsToUpdate) {
|
|
2301
2313
|
if (isCancelled()) {
|
|
2302
|
-
await
|
|
2303
|
-
sendEvent({ type: "complete", renamed, newPath, cancelled: true });
|
|
2304
|
-
controller.close();
|
|
2314
|
+
await handleRenameCancel();
|
|
2305
2315
|
return;
|
|
2306
2316
|
}
|
|
2307
2317
|
const { oldKey: oldKey2, newKey: newKey2, entry: entry2 } = item;
|
|
@@ -2326,6 +2336,7 @@ async function handleRenameStream(request) {
|
|
|
2326
2336
|
}
|
|
2327
2337
|
delete meta[oldKey2];
|
|
2328
2338
|
meta[newKey2] = entry2;
|
|
2339
|
+
await saveMeta(meta);
|
|
2329
2340
|
renamed++;
|
|
2330
2341
|
sendEvent({
|
|
2331
2342
|
type: "progress",
|
|
@@ -2335,7 +2346,6 @@ async function handleRenameStream(request) {
|
|
|
2335
2346
|
message: `Renamed ${path7.basename(newKey2)}`
|
|
2336
2347
|
});
|
|
2337
2348
|
}
|
|
2338
|
-
await saveMeta(meta);
|
|
2339
2349
|
await deleteEmptyFolders(absoluteOldPath);
|
|
2340
2350
|
const oldThumbFolder = path7.join(getPublicPath("/images"), oldRelativePath);
|
|
2341
2351
|
await deleteEmptyFolders(oldThumbFolder);
|
|
@@ -2351,7 +2361,7 @@ async function handleRenameStream(request) {
|
|
|
2351
2361
|
const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;
|
|
2352
2362
|
const hasThumbnails = entry ? isProcessed(entry) : false;
|
|
2353
2363
|
sendEvent({ type: "start", total: 1, message: "Renaming file..." });
|
|
2354
|
-
if (isInOurR2 && !hasLocalItem
|
|
2364
|
+
if (isInOurR2 && !hasLocalItem) {
|
|
2355
2365
|
await moveInCdn(oldKey, newKey, hasThumbnails);
|
|
2356
2366
|
delete meta[oldKey];
|
|
2357
2367
|
if (entry) meta[newKey] = entry;
|
|
@@ -2508,18 +2518,26 @@ async function handleMoveStream(request) {
|
|
|
2508
2518
|
}
|
|
2509
2519
|
sendEvent({ type: "start", total: totalFiles });
|
|
2510
2520
|
let processedFiles = 0;
|
|
2521
|
+
let filesMoved = 0;
|
|
2522
|
+
const handleCancel = async () => {
|
|
2523
|
+
await saveMeta(meta);
|
|
2524
|
+
for (const folder of sourceFolders) {
|
|
2525
|
+
await deleteEmptyFolders(folder);
|
|
2526
|
+
}
|
|
2527
|
+
await deleteEmptyFolders(absoluteDestination);
|
|
2528
|
+
sendEvent({ type: "complete", moved: filesMoved, errors: errors.length, errorMessages: errors, cancelled: true });
|
|
2529
|
+
controller.close();
|
|
2530
|
+
};
|
|
2511
2531
|
for (const expandedItem of expandedItems) {
|
|
2512
2532
|
if (isCancelled()) {
|
|
2513
|
-
|
|
2514
|
-
controller.close();
|
|
2533
|
+
await handleCancel();
|
|
2515
2534
|
return;
|
|
2516
2535
|
}
|
|
2517
2536
|
const { itemPath, safePath, itemName, oldKey, newKey, newAbsolutePath, isVirtualFolder, virtualFolderItems } = expandedItem;
|
|
2518
2537
|
if (isVirtualFolder && virtualFolderItems) {
|
|
2519
2538
|
for (const vItem of virtualFolderItems) {
|
|
2520
2539
|
if (isCancelled()) {
|
|
2521
|
-
|
|
2522
|
-
controller.close();
|
|
2540
|
+
await handleCancel();
|
|
2523
2541
|
return;
|
|
2524
2542
|
}
|
|
2525
2543
|
const itemEntry = vItem.entry;
|
|
@@ -2532,21 +2550,26 @@ async function handleMoveStream(request) {
|
|
|
2532
2550
|
try {
|
|
2533
2551
|
await moveInCdn(vItem.oldKey, vItem.newKey, itemHasThumbnails);
|
|
2534
2552
|
vItemMoved = true;
|
|
2553
|
+
filesMoved++;
|
|
2535
2554
|
} catch (err) {
|
|
2536
2555
|
console.error(`Failed to move cloud item ${vItem.oldKey}:`, err);
|
|
2537
2556
|
delete meta[vItem.oldKey];
|
|
2557
|
+
await saveMeta(meta);
|
|
2538
2558
|
}
|
|
2539
2559
|
}
|
|
2540
2560
|
if (vItemMoved) {
|
|
2541
2561
|
delete meta[vItem.oldKey];
|
|
2542
2562
|
meta[vItem.newKey] = itemEntry;
|
|
2563
|
+
await saveMeta(meta);
|
|
2564
|
+
const oldAbsPath = getPublicPath(vItem.oldKey);
|
|
2565
|
+
sourceFolders.add(path7.dirname(oldAbsPath));
|
|
2543
2566
|
}
|
|
2544
2567
|
processedFiles++;
|
|
2545
2568
|
sendEvent({
|
|
2546
2569
|
type: "progress",
|
|
2547
2570
|
current: processedFiles,
|
|
2548
2571
|
total: totalFiles,
|
|
2549
|
-
moved:
|
|
2572
|
+
moved: filesMoved,
|
|
2550
2573
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2551
2574
|
currentFile: path7.basename(vItem.newKey)
|
|
2552
2575
|
});
|
|
@@ -2555,6 +2578,8 @@ async function handleMoveStream(request) {
|
|
|
2555
2578
|
await deleteEmptyFolders(newFolderPath);
|
|
2556
2579
|
const newThumbFolder = path7.join(getPublicPath("images"), newKey.slice(1));
|
|
2557
2580
|
await deleteEmptyFolders(newThumbFolder);
|
|
2581
|
+
const oldFolderPath = getPublicPath(oldKey);
|
|
2582
|
+
sourceFolders.add(oldFolderPath);
|
|
2558
2583
|
moved.push(itemPath);
|
|
2559
2584
|
continue;
|
|
2560
2585
|
}
|
|
@@ -2581,7 +2606,7 @@ async function handleMoveStream(request) {
|
|
|
2581
2606
|
try {
|
|
2582
2607
|
const sourceFolder = path7.dirname(getWorkspacePath(safePath));
|
|
2583
2608
|
sourceFolders.add(sourceFolder);
|
|
2584
|
-
if (isRemote
|
|
2609
|
+
if (isRemote) {
|
|
2585
2610
|
const remoteUrl = `${fileCdnUrl}${oldKey}`;
|
|
2586
2611
|
const buffer = await downloadFromRemoteUrl(remoteUrl);
|
|
2587
2612
|
await fs7.mkdir(path7.dirname(newAbsolutePath), { recursive: true });
|
|
@@ -2592,29 +2617,33 @@ async function handleMoveStream(request) {
|
|
|
2592
2617
|
};
|
|
2593
2618
|
delete meta[oldKey];
|
|
2594
2619
|
meta[newKey] = newEntry;
|
|
2620
|
+
await saveMeta(meta);
|
|
2595
2621
|
moved.push(itemPath);
|
|
2622
|
+
filesMoved++;
|
|
2596
2623
|
processedFiles++;
|
|
2597
2624
|
sendEvent({
|
|
2598
2625
|
type: "progress",
|
|
2599
2626
|
current: processedFiles,
|
|
2600
2627
|
total: totalFiles,
|
|
2601
|
-
moved:
|
|
2628
|
+
moved: filesMoved,
|
|
2602
2629
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2603
2630
|
currentFile: itemName
|
|
2604
2631
|
});
|
|
2605
|
-
} else if (isPushedToR2
|
|
2632
|
+
} else if (isPushedToR2) {
|
|
2606
2633
|
await moveInCdn(oldKey, newKey, hasProcessedThumbnails);
|
|
2607
2634
|
delete meta[oldKey];
|
|
2608
2635
|
if (entry) {
|
|
2609
2636
|
meta[newKey] = entry;
|
|
2610
2637
|
}
|
|
2638
|
+
await saveMeta(meta);
|
|
2611
2639
|
moved.push(itemPath);
|
|
2640
|
+
filesMoved++;
|
|
2612
2641
|
processedFiles++;
|
|
2613
2642
|
sendEvent({
|
|
2614
2643
|
type: "progress",
|
|
2615
2644
|
current: processedFiles,
|
|
2616
2645
|
total: totalFiles,
|
|
2617
|
-
moved:
|
|
2646
|
+
moved: filesMoved,
|
|
2618
2647
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2619
2648
|
currentFile: itemName
|
|
2620
2649
|
});
|
|
@@ -2627,7 +2656,7 @@ async function handleMoveStream(request) {
|
|
|
2627
2656
|
type: "progress",
|
|
2628
2657
|
current: processedFiles,
|
|
2629
2658
|
total: totalFiles,
|
|
2630
|
-
moved:
|
|
2659
|
+
moved: filesMoved,
|
|
2631
2660
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2632
2661
|
currentFile: itemName
|
|
2633
2662
|
});
|
|
@@ -2642,7 +2671,7 @@ async function handleMoveStream(request) {
|
|
|
2642
2671
|
type: "progress",
|
|
2643
2672
|
current: processedFiles,
|
|
2644
2673
|
total: totalFiles,
|
|
2645
|
-
moved:
|
|
2674
|
+
moved: filesMoved,
|
|
2646
2675
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2647
2676
|
currentFile: itemName
|
|
2648
2677
|
});
|
|
@@ -2656,7 +2685,7 @@ async function handleMoveStream(request) {
|
|
|
2656
2685
|
type: "progress",
|
|
2657
2686
|
current: processedFiles,
|
|
2658
2687
|
total: totalFiles,
|
|
2659
|
-
moved:
|
|
2688
|
+
moved: filesMoved,
|
|
2660
2689
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2661
2690
|
currentFile: itemName
|
|
2662
2691
|
});
|
|
@@ -2694,17 +2723,19 @@ async function handleMoveStream(request) {
|
|
|
2694
2723
|
}
|
|
2695
2724
|
delete meta[oldKey];
|
|
2696
2725
|
meta[newKey] = entry;
|
|
2726
|
+
await saveMeta(meta);
|
|
2697
2727
|
}
|
|
2728
|
+
moved.push(itemPath);
|
|
2729
|
+
filesMoved++;
|
|
2698
2730
|
processedFiles++;
|
|
2699
2731
|
sendEvent({
|
|
2700
2732
|
type: "progress",
|
|
2701
2733
|
current: processedFiles,
|
|
2702
2734
|
total: totalFiles,
|
|
2703
|
-
moved:
|
|
2735
|
+
moved: filesMoved,
|
|
2704
2736
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2705
2737
|
currentFile: itemName
|
|
2706
2738
|
});
|
|
2707
|
-
moved.push(itemPath);
|
|
2708
2739
|
} else if (stats.isDirectory()) {
|
|
2709
2740
|
const oldPrefix = oldKey + "/";
|
|
2710
2741
|
const newPrefix = newKey + "/";
|
|
@@ -2739,9 +2770,7 @@ async function handleMoveStream(request) {
|
|
|
2739
2770
|
}
|
|
2740
2771
|
for (const localFile of localFiles) {
|
|
2741
2772
|
if (isCancelled()) {
|
|
2742
|
-
await
|
|
2743
|
-
sendEvent({ type: "complete", moved: moved.length, errors: errors.length, errorMessages: errors, cancelled: true });
|
|
2744
|
-
controller.close();
|
|
2773
|
+
await handleCancel();
|
|
2745
2774
|
return;
|
|
2746
2775
|
}
|
|
2747
2776
|
const fileOldPath = path7.join(absolutePath, localFile.relativePath);
|
|
@@ -2752,6 +2781,7 @@ async function handleMoveStream(request) {
|
|
|
2752
2781
|
sourceFolders.add(path7.dirname(fileOldPath));
|
|
2753
2782
|
await fs7.mkdir(path7.dirname(fileNewPath), { recursive: true });
|
|
2754
2783
|
await fs7.rename(fileOldPath, fileNewPath);
|
|
2784
|
+
filesMoved++;
|
|
2755
2785
|
if (localFile.isImage && fileEntry) {
|
|
2756
2786
|
const oldThumbPaths = getAllThumbnailPaths(fileOldKey);
|
|
2757
2787
|
const newThumbPaths = getAllThumbnailPaths(fileNewKey);
|
|
@@ -2775,22 +2805,21 @@ async function handleMoveStream(request) {
|
|
|
2775
2805
|
}
|
|
2776
2806
|
delete meta[fileOldKey];
|
|
2777
2807
|
meta[fileNewKey] = fileEntry;
|
|
2808
|
+
await saveMeta(meta);
|
|
2778
2809
|
}
|
|
2779
2810
|
processedFiles++;
|
|
2780
2811
|
sendEvent({
|
|
2781
2812
|
type: "progress",
|
|
2782
2813
|
current: processedFiles,
|
|
2783
2814
|
total: totalFiles,
|
|
2784
|
-
moved:
|
|
2815
|
+
moved: filesMoved,
|
|
2785
2816
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2786
2817
|
currentFile: path7.basename(localFile.relativePath)
|
|
2787
2818
|
});
|
|
2788
2819
|
}
|
|
2789
2820
|
for (const cloudFile of cloudOnlyFiles) {
|
|
2790
2821
|
if (isCancelled()) {
|
|
2791
|
-
await
|
|
2792
|
-
sendEvent({ type: "complete", moved: moved.length, errors: errors.length, errorMessages: errors, cancelled: true });
|
|
2793
|
-
controller.close();
|
|
2822
|
+
await handleCancel();
|
|
2794
2823
|
return;
|
|
2795
2824
|
}
|
|
2796
2825
|
const cloudEntry = cloudFile.entry;
|
|
@@ -2803,21 +2832,24 @@ async function handleMoveStream(request) {
|
|
|
2803
2832
|
try {
|
|
2804
2833
|
await moveInCdn(cloudFile.oldKey, cloudFile.newKey, cloudHasThumbs);
|
|
2805
2834
|
cloudFileMoved = true;
|
|
2835
|
+
filesMoved++;
|
|
2806
2836
|
} catch (err) {
|
|
2807
2837
|
console.error(`Failed to move cloud file ${cloudFile.oldKey}:`, err);
|
|
2808
2838
|
delete meta[cloudFile.oldKey];
|
|
2839
|
+
await saveMeta(meta);
|
|
2809
2840
|
}
|
|
2810
2841
|
}
|
|
2811
2842
|
if (cloudFileMoved) {
|
|
2812
2843
|
delete meta[cloudFile.oldKey];
|
|
2813
2844
|
meta[cloudFile.newKey] = cloudEntry;
|
|
2845
|
+
await saveMeta(meta);
|
|
2814
2846
|
}
|
|
2815
2847
|
processedFiles++;
|
|
2816
2848
|
sendEvent({
|
|
2817
2849
|
type: "progress",
|
|
2818
2850
|
current: processedFiles,
|
|
2819
2851
|
total: totalFiles,
|
|
2820
|
-
moved:
|
|
2852
|
+
moved: filesMoved,
|
|
2821
2853
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2822
2854
|
currentFile: path7.basename(cloudFile.newKey)
|
|
2823
2855
|
});
|
|
@@ -2837,7 +2869,7 @@ async function handleMoveStream(request) {
|
|
|
2837
2869
|
type: "progress",
|
|
2838
2870
|
current: processedFiles,
|
|
2839
2871
|
total: totalFiles,
|
|
2840
|
-
moved:
|
|
2872
|
+
moved: filesMoved,
|
|
2841
2873
|
percent: Math.round(processedFiles / totalFiles * 100),
|
|
2842
2874
|
currentFile: itemName
|
|
2843
2875
|
});
|
|
@@ -2850,7 +2882,7 @@ async function handleMoveStream(request) {
|
|
|
2850
2882
|
await deleteEmptyFolders(absoluteDestination);
|
|
2851
2883
|
sendEvent({
|
|
2852
2884
|
type: "complete",
|
|
2853
|
-
moved:
|
|
2885
|
+
moved: filesMoved,
|
|
2854
2886
|
errors: errors.length,
|
|
2855
2887
|
errorMessages: errors
|
|
2856
2888
|
});
|
|
@@ -3002,6 +3034,9 @@ async function handleScanStream() {
|
|
|
3002
3034
|
}
|
|
3003
3035
|
existingKeys.add(imageKey);
|
|
3004
3036
|
added.push(imageKey);
|
|
3037
|
+
if (added.length % 10 === 0) {
|
|
3038
|
+
await saveMeta(meta);
|
|
3039
|
+
}
|
|
3005
3040
|
} catch (error) {
|
|
3006
3041
|
console.error(`Failed to process ${relativePath}:`, error);
|
|
3007
3042
|
errors.push(relativePath);
|
|
@@ -3452,6 +3487,9 @@ async function handleGenerateFavicon(request) {
|
|
|
3452
3487
|
// src/server/index.ts
|
|
3453
3488
|
var __filename = fileURLToPath(import.meta.url);
|
|
3454
3489
|
var __dirname = dirname(__filename);
|
|
3490
|
+
var packageJsonPath = resolve(__dirname, "../../package.json");
|
|
3491
|
+
var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
3492
|
+
var version = packageJson.version;
|
|
3455
3493
|
function isPortAvailable(port) {
|
|
3456
3494
|
return new Promise((resolve2) => {
|
|
3457
3495
|
const server = createServer();
|
|
@@ -3542,10 +3580,11 @@ async function startServer(options) {
|
|
|
3542
3580
|
}
|
|
3543
3581
|
});
|
|
3544
3582
|
app.use(express.static(clientDir));
|
|
3583
|
+
const title = `Gallop - Studio (${version})`;
|
|
3545
3584
|
app.listen(port, () => {
|
|
3546
3585
|
console.log(`
|
|
3547
3586
|
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
3548
|
-
\u2502
|
|
3587
|
+
\u2502 ${title.padEnd(34)}\u2502
|
|
3549
3588
|
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
3550
3589
|
\u2502 Workspace: ${workspace.length > 24 ? "..." + workspace.slice(-21) : workspace.padEnd(24)}\u2502
|
|
3551
3590
|
\u2502 URL: http://localhost:${port} \u2502
|