unity-hub-cli 0.6.1 → 0.8.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 +166 -144
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,28 +6,42 @@ import { render } from "ink";
|
|
|
6
6
|
|
|
7
7
|
// src/application/usecases.ts
|
|
8
8
|
var ListProjectsUseCase = class {
|
|
9
|
-
constructor(unityHubProjectsReader, gitRepositoryInfoReader, unityProjectOptionsReader, lockReader) {
|
|
9
|
+
constructor(unityHubProjectsReader, gitRepositoryInfoReader, unityProjectOptionsReader, lockReader, unityProcessReader) {
|
|
10
10
|
this.unityHubProjectsReader = unityHubProjectsReader;
|
|
11
11
|
this.gitRepositoryInfoReader = gitRepositoryInfoReader;
|
|
12
12
|
this.unityProjectOptionsReader = unityProjectOptionsReader;
|
|
13
13
|
this.lockReader = lockReader;
|
|
14
|
+
this.unityProcessReader = unityProcessReader;
|
|
14
15
|
}
|
|
15
16
|
async execute() {
|
|
16
17
|
const projects = await this.unityHubProjectsReader.listProjects();
|
|
17
|
-
const [repositoryInfoResults, lockResults] = await Promise.all([
|
|
18
|
+
const [repositoryInfoResults, lockResults, processResults] = await Promise.all([
|
|
18
19
|
Promise.allSettled(
|
|
19
20
|
projects.map((project) => this.gitRepositoryInfoReader.readRepositoryInfo(project.path))
|
|
20
21
|
),
|
|
21
|
-
Promise.allSettled(projects.map((project) => this.lockReader.isLocked(project.path)))
|
|
22
|
+
Promise.allSettled(projects.map((project) => this.lockReader.isLocked(project.path))),
|
|
23
|
+
Promise.allSettled(projects.map((project) => this.unityProcessReader.findByProjectPath(project.path)))
|
|
22
24
|
]);
|
|
23
25
|
return projects.map((project, index) => {
|
|
24
26
|
const repositoryResult = repositoryInfoResults[index];
|
|
25
27
|
const lockResult = lockResults[index];
|
|
28
|
+
const processResult = processResults[index];
|
|
26
29
|
const repository = repositoryResult.status === "fulfilled" ? repositoryResult.value ?? void 0 : void 0;
|
|
27
30
|
const isLocked = lockResult.status === "fulfilled" ? Boolean(lockResult.value) : false;
|
|
28
|
-
|
|
31
|
+
const hasRunningProcess = processResult.status === "fulfilled" ? Boolean(processResult.value) : false;
|
|
32
|
+
const launchStatus = this.determineLaunchStatus(hasRunningProcess, isLocked);
|
|
33
|
+
return { project, repository, isLocked, launchStatus };
|
|
29
34
|
});
|
|
30
35
|
}
|
|
36
|
+
determineLaunchStatus(hasRunningProcess, isLocked) {
|
|
37
|
+
if (hasRunningProcess) {
|
|
38
|
+
return "running";
|
|
39
|
+
}
|
|
40
|
+
if (isLocked) {
|
|
41
|
+
return "crashed";
|
|
42
|
+
}
|
|
43
|
+
return "idle";
|
|
44
|
+
}
|
|
31
45
|
};
|
|
32
46
|
var LaunchCancelledError = class extends Error {
|
|
33
47
|
constructor() {
|
|
@@ -71,24 +85,23 @@ var TerminateProjectUseCase = class {
|
|
|
71
85
|
message: "No Unity process is running for this project."
|
|
72
86
|
};
|
|
73
87
|
}
|
|
74
|
-
const
|
|
75
|
-
if (!terminated) {
|
|
88
|
+
const termination = await this.unityProcessTerminator.terminate(unityProcess);
|
|
89
|
+
if (!termination.terminated) {
|
|
76
90
|
return {
|
|
77
91
|
terminated: false,
|
|
78
92
|
message: "Failed to terminate the Unity process."
|
|
79
93
|
};
|
|
80
94
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
if (termination.stage === "sigterm" || termination.stage === "sigkill") {
|
|
96
|
+
try {
|
|
97
|
+
await this.unityTempDirectoryCleaner.clean(project.path);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
console.error(`Failed to clean Temp directory after termination: ${message}`);
|
|
101
|
+
}
|
|
88
102
|
}
|
|
89
103
|
return {
|
|
90
|
-
terminated: true
|
|
91
|
-
message: cleanupMessage
|
|
104
|
+
terminated: true
|
|
92
105
|
};
|
|
93
106
|
}
|
|
94
107
|
};
|
|
@@ -348,117 +361,14 @@ var UnityHubProjectsReader = class {
|
|
|
348
361
|
|
|
349
362
|
// src/infrastructure/unityLock.ts
|
|
350
363
|
import { execFile } from "child_process";
|
|
351
|
-
import { constants as constants2
|
|
364
|
+
import { constants as constants2 } from "fs";
|
|
352
365
|
import { access as access2, rm } from "fs/promises";
|
|
353
366
|
import { join as join3 } from "path";
|
|
354
|
-
import readline from "readline";
|
|
355
367
|
import { promisify } from "util";
|
|
356
|
-
var RAW_PROMPT_MESSAGE = "Delete UnityLockfile and continue? Type 'y' to continue; anything else aborts: ";
|
|
357
368
|
var execFileAsync = promisify(execFile);
|
|
358
369
|
var buildBringToFrontScript = (pid) => {
|
|
359
370
|
return `tell application "System Events" to set frontmost of (first process whose unix id is ${pid}) to true`;
|
|
360
371
|
};
|
|
361
|
-
var isRawModeSupported = () => {
|
|
362
|
-
const stdin = process.stdin;
|
|
363
|
-
return Boolean(stdin?.isTTY && typeof stdin.setRawMode === "function" && process.stdout.isTTY);
|
|
364
|
-
};
|
|
365
|
-
var createPromptInterface = () => {
|
|
366
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
367
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
368
|
-
const close = () => rl.close();
|
|
369
|
-
return { rl, close };
|
|
370
|
-
}
|
|
371
|
-
try {
|
|
372
|
-
if (process.platform === "win32") {
|
|
373
|
-
const inCandidates = ["\\\\.\\CONIN$", "CONIN$"];
|
|
374
|
-
const outCandidates = ["\\\\.\\CONOUT$", "CONOUT$"];
|
|
375
|
-
for (const inPath of inCandidates) {
|
|
376
|
-
for (const outPath of outCandidates) {
|
|
377
|
-
try {
|
|
378
|
-
const input = createReadStream(inPath);
|
|
379
|
-
const output = createWriteStream(outPath);
|
|
380
|
-
const rl = readline.createInterface({ input, output });
|
|
381
|
-
const close = () => {
|
|
382
|
-
rl.close();
|
|
383
|
-
input.destroy();
|
|
384
|
-
output.end();
|
|
385
|
-
};
|
|
386
|
-
return { rl, close };
|
|
387
|
-
} catch {
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
} else {
|
|
393
|
-
const input = createReadStream("/dev/tty");
|
|
394
|
-
const output = createWriteStream("/dev/tty");
|
|
395
|
-
const rl = readline.createInterface({ input, output });
|
|
396
|
-
const close = () => {
|
|
397
|
-
rl.close();
|
|
398
|
-
input.destroy();
|
|
399
|
-
output.end();
|
|
400
|
-
};
|
|
401
|
-
return { rl, close };
|
|
402
|
-
}
|
|
403
|
-
} catch {
|
|
404
|
-
return void 0;
|
|
405
|
-
}
|
|
406
|
-
return void 0;
|
|
407
|
-
};
|
|
408
|
-
var promptYesNoSingleKey = async () => {
|
|
409
|
-
const stdin = process.stdin;
|
|
410
|
-
const supportsRaw = isRawModeSupported();
|
|
411
|
-
const previousRaw = supportsRaw ? stdin.isRaw === true : false;
|
|
412
|
-
const wasPaused = stdin.isPaused();
|
|
413
|
-
return await new Promise((resolve3) => {
|
|
414
|
-
const cleanup = () => {
|
|
415
|
-
stdin.removeListener("data", handleData);
|
|
416
|
-
if (wasPaused) {
|
|
417
|
-
stdin.pause();
|
|
418
|
-
}
|
|
419
|
-
if (supportsRaw) {
|
|
420
|
-
stdin.setRawMode(previousRaw);
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
const handleData = (data) => {
|
|
424
|
-
const char = data.toString();
|
|
425
|
-
const firstByte = data[0] ?? 0;
|
|
426
|
-
let result = false;
|
|
427
|
-
if (char === "y") {
|
|
428
|
-
result = true;
|
|
429
|
-
} else if (char === "n" || char === "N" || firstByte === 3 || firstByte === 27 || firstByte === 13) {
|
|
430
|
-
result = false;
|
|
431
|
-
} else {
|
|
432
|
-
result = false;
|
|
433
|
-
}
|
|
434
|
-
process.stdout.write("\n");
|
|
435
|
-
cleanup();
|
|
436
|
-
resolve3(result);
|
|
437
|
-
};
|
|
438
|
-
process.stdout.write(RAW_PROMPT_MESSAGE);
|
|
439
|
-
if (supportsRaw) {
|
|
440
|
-
stdin.setRawMode(true);
|
|
441
|
-
}
|
|
442
|
-
if (wasPaused) {
|
|
443
|
-
stdin.resume();
|
|
444
|
-
}
|
|
445
|
-
stdin.once("data", handleData);
|
|
446
|
-
});
|
|
447
|
-
};
|
|
448
|
-
var promptYesNoLine = async () => {
|
|
449
|
-
const prompt = createPromptInterface();
|
|
450
|
-
if (!prompt) {
|
|
451
|
-
console.error("UnityLockfile exists. No interactive console available for confirmation.");
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
const confirmed = await new Promise((resolve3) => {
|
|
455
|
-
prompt.rl.question(RAW_PROMPT_MESSAGE, (answer) => {
|
|
456
|
-
resolve3(answer.trim() === "y");
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
prompt.close();
|
|
460
|
-
return confirmed;
|
|
461
|
-
};
|
|
462
372
|
var pathExists = async (target) => {
|
|
463
373
|
try {
|
|
464
374
|
await access2(target, constants2.F_OK);
|
|
@@ -468,8 +378,9 @@ var pathExists = async (target) => {
|
|
|
468
378
|
}
|
|
469
379
|
};
|
|
470
380
|
var UnityLockChecker = class {
|
|
471
|
-
constructor(unityProcessReader) {
|
|
381
|
+
constructor(unityProcessReader, tempDirectoryCleaner) {
|
|
472
382
|
this.unityProcessReader = unityProcessReader;
|
|
383
|
+
this.tempDirectoryCleaner = tempDirectoryCleaner;
|
|
473
384
|
}
|
|
474
385
|
async check(projectPath) {
|
|
475
386
|
const activeProcess = await this.unityProcessReader.findByProjectPath(projectPath);
|
|
@@ -485,15 +396,22 @@ var UnityLockChecker = class {
|
|
|
485
396
|
if (!hasLockfile) {
|
|
486
397
|
return "allow";
|
|
487
398
|
}
|
|
488
|
-
console.log(`UnityLockfile found: ${lockfilePath}`);
|
|
489
|
-
console.log("
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
399
|
+
console.log(`UnityLockfile found without active Unity process: ${lockfilePath}`);
|
|
400
|
+
console.log("Assuming previous crash. Cleaning Temp directory and continuing launch.");
|
|
401
|
+
try {
|
|
402
|
+
await this.tempDirectoryCleaner.clean(projectPath);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
405
|
+
console.error(`Failed to clean Temp directory: ${message}`);
|
|
494
406
|
}
|
|
495
|
-
|
|
496
|
-
|
|
407
|
+
try {
|
|
408
|
+
await rm(lockfilePath, { force: true });
|
|
409
|
+
console.log("Deleted UnityLockfile.");
|
|
410
|
+
} catch (error) {
|
|
411
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
412
|
+
console.error(`Failed to delete UnityLockfile: ${message}`);
|
|
413
|
+
}
|
|
414
|
+
console.log("Continuing launch.");
|
|
497
415
|
return "allow";
|
|
498
416
|
}
|
|
499
417
|
async bringUnityToFront(pid) {
|
|
@@ -527,6 +445,8 @@ var PROCESS_LIST_ARGS = ["-axo", "pid=,command=", "-ww"];
|
|
|
527
445
|
var PROCESS_LIST_COMMAND = "ps";
|
|
528
446
|
var TERMINATE_TIMEOUT_MILLIS = 5e3;
|
|
529
447
|
var TERMINATE_POLL_INTERVAL_MILLIS = 200;
|
|
448
|
+
var GRACEFUL_QUIT_TIMEOUT_MILLIS = 3e3;
|
|
449
|
+
var GRACEFUL_QUIT_POLL_INTERVAL_MILLIS = 200;
|
|
530
450
|
var delay = async (duration) => {
|
|
531
451
|
await new Promise((resolveDelay) => {
|
|
532
452
|
setTimeout(() => {
|
|
@@ -626,11 +546,33 @@ var MacUnityProcessReader = class {
|
|
|
626
546
|
};
|
|
627
547
|
var MacUnityProcessTerminator = class {
|
|
628
548
|
async terminate(unityProcess) {
|
|
549
|
+
let attemptedGraceful = false;
|
|
550
|
+
if (process.platform === "darwin") {
|
|
551
|
+
attemptedGraceful = true;
|
|
552
|
+
try {
|
|
553
|
+
const script = [
|
|
554
|
+
'tell application "System Events"',
|
|
555
|
+
` set frontmost of (first process whose unix id is ${unityProcess.pid}) to true`,
|
|
556
|
+
' keystroke "q" using {command down}',
|
|
557
|
+
"end tell"
|
|
558
|
+
].join("\n");
|
|
559
|
+
await execFileAsync2("osascript", ["-e", script]);
|
|
560
|
+
const deadlineGraceful = Date.now() + GRACEFUL_QUIT_TIMEOUT_MILLIS;
|
|
561
|
+
while (Date.now() < deadlineGraceful) {
|
|
562
|
+
await delay(GRACEFUL_QUIT_POLL_INTERVAL_MILLIS);
|
|
563
|
+
const alive = ensureProcessAlive(unityProcess.pid);
|
|
564
|
+
if (!alive) {
|
|
565
|
+
return { terminated: true, stage: "graceful" };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
570
|
+
}
|
|
629
571
|
try {
|
|
630
572
|
process.kill(unityProcess.pid, "SIGTERM");
|
|
631
573
|
} catch (error) {
|
|
632
574
|
if (isProcessMissingError(error)) {
|
|
633
|
-
return
|
|
575
|
+
return { terminated: true, stage: attemptedGraceful ? "graceful" : "sigterm" };
|
|
634
576
|
}
|
|
635
577
|
throw new Error(
|
|
636
578
|
`Failed to terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -641,21 +583,22 @@ var MacUnityProcessTerminator = class {
|
|
|
641
583
|
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
642
584
|
const alive = ensureProcessAlive(unityProcess.pid);
|
|
643
585
|
if (!alive) {
|
|
644
|
-
return true;
|
|
586
|
+
return { terminated: true, stage: "sigterm" };
|
|
645
587
|
}
|
|
646
588
|
}
|
|
647
589
|
try {
|
|
648
590
|
process.kill(unityProcess.pid, "SIGKILL");
|
|
649
591
|
} catch (error) {
|
|
650
592
|
if (isProcessMissingError(error)) {
|
|
651
|
-
return true;
|
|
593
|
+
return { terminated: true, stage: "sigkill" };
|
|
652
594
|
}
|
|
653
595
|
throw new Error(
|
|
654
596
|
`Failed to forcefully terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
655
597
|
);
|
|
656
598
|
}
|
|
657
599
|
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
658
|
-
|
|
600
|
+
const aliveAfterKill = ensureProcessAlive(unityProcess.pid);
|
|
601
|
+
return aliveAfterKill ? { terminated: false } : { terminated: true, stage: "sigkill" };
|
|
659
602
|
}
|
|
660
603
|
};
|
|
661
604
|
|
|
@@ -764,12 +707,16 @@ var formatUpdatedText = (lastModified) => {
|
|
|
764
707
|
var homeDirectory = process.env.HOME ?? "";
|
|
765
708
|
var homePrefix = homeDirectory ? `${homeDirectory}/` : "";
|
|
766
709
|
var minimumVisibleProjectCount = 4;
|
|
767
|
-
var defaultHintMessage = "Move with arrows or j/k \xB7 Launch with o \xB7 Quit Unity with q \xB7 Copy cd path with c";
|
|
710
|
+
var defaultHintMessage = "Move with arrows or j/k \xB7 Launch with o \xB7 Quit Unity with q \xB7 Refresh with r \xB7 Copy cd path with c";
|
|
768
711
|
var PROJECT_COLOR = "#abd8e7";
|
|
769
712
|
var BRANCH_COLOR = "#e3839c";
|
|
770
713
|
var PATH_COLOR = "#719bd8";
|
|
771
714
|
var LOCK_COLOR = "yellow";
|
|
772
|
-
var
|
|
715
|
+
var STATUS_LABELS = {
|
|
716
|
+
idle: "",
|
|
717
|
+
running: "[running]",
|
|
718
|
+
crashed: "[crash]"
|
|
719
|
+
};
|
|
773
720
|
var shortenHomePath = (targetPath) => {
|
|
774
721
|
if (!homeDirectory) {
|
|
775
722
|
return targetPath;
|
|
@@ -790,18 +737,21 @@ var App = ({
|
|
|
790
737
|
projects,
|
|
791
738
|
onLaunch,
|
|
792
739
|
onTerminate,
|
|
740
|
+
onRefresh,
|
|
793
741
|
useGitRootName = true,
|
|
794
742
|
showBranch = true,
|
|
795
743
|
showPath = true
|
|
796
744
|
}) => {
|
|
797
745
|
const { exit } = useApp();
|
|
798
746
|
const { stdout } = useStdout();
|
|
747
|
+
const [projectViews, setProjectViews] = useState(projects);
|
|
799
748
|
const [visibleCount, setVisibleCount] = useState(minimumVisibleProjectCount);
|
|
800
749
|
const [index, setIndex] = useState(0);
|
|
801
750
|
const [hint, setHint] = useState(defaultHintMessage);
|
|
802
751
|
const [windowStart, setWindowStart] = useState(0);
|
|
803
752
|
const [releasedProjects, setReleasedProjects] = useState(/* @__PURE__ */ new Set());
|
|
804
753
|
const [launchedProjects, setLaunchedProjects] = useState(/* @__PURE__ */ new Set());
|
|
754
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
805
755
|
const linesPerProject = (showBranch ? 1 : 0) + (showPath ? 1 : 0) + 2;
|
|
806
756
|
const sortedProjects = useMemo(() => {
|
|
807
757
|
const fallbackTime = 0;
|
|
@@ -815,7 +765,7 @@ var App = ({
|
|
|
815
765
|
return view.project.title.toLocaleLowerCase();
|
|
816
766
|
};
|
|
817
767
|
const toTieBreaker = (view) => view.project.path.toLocaleLowerCase();
|
|
818
|
-
return [...
|
|
768
|
+
return [...projectViews].sort((a, b) => {
|
|
819
769
|
if (a.project.favorite !== b.project.favorite) {
|
|
820
770
|
return a.project.favorite ? -1 : 1;
|
|
821
771
|
}
|
|
@@ -831,7 +781,7 @@ var App = ({
|
|
|
831
781
|
}
|
|
832
782
|
return keyA.localeCompare(keyB);
|
|
833
783
|
});
|
|
834
|
-
}, [
|
|
784
|
+
}, [projectViews, useGitRootName]);
|
|
835
785
|
useEffect(() => {
|
|
836
786
|
const handleSigint = () => {
|
|
837
787
|
exit();
|
|
@@ -1050,9 +1000,63 @@ var App = ({
|
|
|
1050
1000
|
}
|
|
1051
1001
|
}, [index, onTerminate, sortedProjects]);
|
|
1052
1002
|
useEffect(() => {
|
|
1003
|
+
setProjectViews(projects);
|
|
1053
1004
|
setReleasedProjects(/* @__PURE__ */ new Set());
|
|
1054
1005
|
setLaunchedProjects(/* @__PURE__ */ new Set());
|
|
1055
1006
|
}, [projects]);
|
|
1007
|
+
const refreshProjects = useCallback(async () => {
|
|
1008
|
+
if (!onRefresh) {
|
|
1009
|
+
setHint("Refresh not available");
|
|
1010
|
+
setTimeout(() => {
|
|
1011
|
+
setHint(defaultHintMessage);
|
|
1012
|
+
}, 2e3);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (isRefreshing) {
|
|
1016
|
+
setHint("Already refreshing");
|
|
1017
|
+
setTimeout(() => {
|
|
1018
|
+
setHint(defaultHintMessage);
|
|
1019
|
+
}, 2e3);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
setIsRefreshing(true);
|
|
1023
|
+
setHint("Refreshing projects...");
|
|
1024
|
+
try {
|
|
1025
|
+
const updatedProjects = await onRefresh();
|
|
1026
|
+
setProjectViews(updatedProjects);
|
|
1027
|
+
setReleasedProjects(/* @__PURE__ */ new Set());
|
|
1028
|
+
setLaunchedProjects(/* @__PURE__ */ new Set());
|
|
1029
|
+
setIndex((previousIndex) => {
|
|
1030
|
+
if (updatedProjects.length === 0) {
|
|
1031
|
+
return 0;
|
|
1032
|
+
}
|
|
1033
|
+
const previousProject = sortedProjects[previousIndex]?.project;
|
|
1034
|
+
if (!previousProject) {
|
|
1035
|
+
return Math.min(previousIndex, updatedProjects.length - 1);
|
|
1036
|
+
}
|
|
1037
|
+
const nextIndex = updatedProjects.findIndex(
|
|
1038
|
+
(candidate) => candidate.project.id === previousProject.id
|
|
1039
|
+
);
|
|
1040
|
+
if (nextIndex === -1) {
|
|
1041
|
+
return Math.min(previousIndex, updatedProjects.length - 1);
|
|
1042
|
+
}
|
|
1043
|
+
return nextIndex;
|
|
1044
|
+
});
|
|
1045
|
+
setWindowStart(0);
|
|
1046
|
+
setHint("Project list refreshed");
|
|
1047
|
+
setTimeout(() => {
|
|
1048
|
+
setHint(defaultHintMessage);
|
|
1049
|
+
}, 2e3);
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1052
|
+
setHint(`Failed to refresh: ${message}`);
|
|
1053
|
+
setTimeout(() => {
|
|
1054
|
+
setHint(defaultHintMessage);
|
|
1055
|
+
}, 3e3);
|
|
1056
|
+
} finally {
|
|
1057
|
+
setIsRefreshing(false);
|
|
1058
|
+
}
|
|
1059
|
+
}, [isRefreshing, onRefresh, sortedProjects]);
|
|
1056
1060
|
useInput((input, key) => {
|
|
1057
1061
|
if (input === "j" || key.downArrow) {
|
|
1058
1062
|
move(1);
|
|
@@ -1068,6 +1072,10 @@ var App = ({
|
|
|
1068
1072
|
void launchSelected();
|
|
1069
1073
|
return;
|
|
1070
1074
|
}
|
|
1075
|
+
if (input === "r") {
|
|
1076
|
+
void refreshProjects();
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1071
1079
|
if (input === "c") {
|
|
1072
1080
|
copyProjectPath();
|
|
1073
1081
|
}
|
|
@@ -1088,7 +1096,7 @@ var App = ({
|
|
|
1088
1096
|
};
|
|
1089
1097
|
}, [limit, sortedProjects, windowStart]);
|
|
1090
1098
|
const scrollbarChars = useMemo(() => {
|
|
1091
|
-
const totalProjects =
|
|
1099
|
+
const totalProjects = projectViews.length;
|
|
1092
1100
|
const totalLines = totalProjects * linesPerProject;
|
|
1093
1101
|
const windowProjects = visibleProjects.length;
|
|
1094
1102
|
const visibleLines = windowProjects * linesPerProject;
|
|
@@ -1113,9 +1121,9 @@ var App = ({
|
|
|
1113
1121
|
}
|
|
1114
1122
|
return "|";
|
|
1115
1123
|
});
|
|
1116
|
-
}, [linesPerProject,
|
|
1124
|
+
}, [linesPerProject, projectViews.length, startIndex, visibleProjects]);
|
|
1117
1125
|
const rows = useMemo(() => {
|
|
1118
|
-
return visibleProjects.map(({ project, repository,
|
|
1126
|
+
return visibleProjects.map(({ project, repository, launchStatus }, offset) => {
|
|
1119
1127
|
const rowIndex = startIndex + offset;
|
|
1120
1128
|
const isSelected = rowIndex === index;
|
|
1121
1129
|
const arrow = isSelected ? ">" : " ";
|
|
@@ -1124,12 +1132,24 @@ var App = ({
|
|
|
1124
1132
|
const updatedText = formatUpdatedText(project.lastModified);
|
|
1125
1133
|
const pathLine = shortenHomePath(project.path);
|
|
1126
1134
|
const branchLine = formatBranch(repository?.branch);
|
|
1127
|
-
const
|
|
1135
|
+
const hasReleasedLock = releasedProjects.has(project.id);
|
|
1136
|
+
const isLocallyLaunched = launchedProjects.has(project.id);
|
|
1137
|
+
const displayStatus = (() => {
|
|
1138
|
+
if (isLocallyLaunched) {
|
|
1139
|
+
return "running";
|
|
1140
|
+
}
|
|
1141
|
+
if (hasReleasedLock) {
|
|
1142
|
+
return "idle";
|
|
1143
|
+
}
|
|
1144
|
+
return launchStatus;
|
|
1145
|
+
})();
|
|
1128
1146
|
const baseScrollbarIndex = offset * linesPerProject;
|
|
1129
1147
|
const titleScrollbar = scrollbarChars[baseScrollbarIndex] ?? " ";
|
|
1130
1148
|
const branchScrollbar = showBranch ? scrollbarChars[baseScrollbarIndex + 1] ?? " " : " ";
|
|
1131
1149
|
const pathScrollbar = showPath ? scrollbarChars[baseScrollbarIndex + 1 + (showBranch ? 1 : 0)] ?? " " : " ";
|
|
1132
1150
|
const spacerScrollbar = scrollbarChars[baseScrollbarIndex + linesPerProject - 1] ?? " ";
|
|
1151
|
+
const statusLabel = STATUS_LABELS[displayStatus];
|
|
1152
|
+
const statusColor = displayStatus === "running" ? LOCK_COLOR : displayStatus === "crashed" ? "red" : void 0;
|
|
1133
1153
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1134
1154
|
/* @__PURE__ */ jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [
|
|
1135
1155
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -1143,7 +1163,7 @@ var App = ({
|
|
|
1143
1163
|
versionLabel
|
|
1144
1164
|
] }),
|
|
1145
1165
|
updatedText ? /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: ` ${updatedText}` }) : null,
|
|
1146
|
-
|
|
1166
|
+
statusLabel && statusColor ? /* @__PURE__ */ jsx(Text, { color: statusColor, children: ` ${statusLabel}` }) : null
|
|
1147
1167
|
] }),
|
|
1148
1168
|
showBranch ? /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : BRANCH_COLOR, children: [
|
|
1149
1169
|
" ",
|
|
@@ -1187,17 +1207,18 @@ var bootstrap = async () => {
|
|
|
1187
1207
|
const gitRepositoryInfoReader = new GitRepositoryInfoReader();
|
|
1188
1208
|
const lockStatusReader = new UnityLockStatusReader();
|
|
1189
1209
|
const unityProcessReader = new MacUnityProcessReader();
|
|
1210
|
+
const unityTempDirectoryCleaner = new UnityTempDirectoryCleaner();
|
|
1190
1211
|
const listProjectsUseCase = new ListProjectsUseCase(
|
|
1191
1212
|
unityHubReader,
|
|
1192
1213
|
gitRepositoryInfoReader,
|
|
1193
1214
|
unityHubReader,
|
|
1194
|
-
lockStatusReader
|
|
1215
|
+
lockStatusReader,
|
|
1216
|
+
unityProcessReader
|
|
1195
1217
|
);
|
|
1196
1218
|
const editorPathResolver = new MacEditorPathResolver();
|
|
1197
1219
|
const processLauncher = new NodeProcessLauncher();
|
|
1198
|
-
const lockChecker = new UnityLockChecker(unityProcessReader);
|
|
1220
|
+
const lockChecker = new UnityLockChecker(unityProcessReader, unityTempDirectoryCleaner);
|
|
1199
1221
|
const unityProcessTerminator = new MacUnityProcessTerminator();
|
|
1200
|
-
const unityTempDirectoryCleaner = new UnityTempDirectoryCleaner();
|
|
1201
1222
|
const launchProjectUseCase = new LaunchProjectUseCase(
|
|
1202
1223
|
editorPathResolver,
|
|
1203
1224
|
processLauncher,
|
|
@@ -1222,6 +1243,7 @@ var bootstrap = async () => {
|
|
|
1222
1243
|
projects,
|
|
1223
1244
|
onLaunch: (project) => launchProjectUseCase.execute(project),
|
|
1224
1245
|
onTerminate: (project) => terminateProjectUseCase.execute(project),
|
|
1246
|
+
onRefresh: () => listProjectsUseCase.execute(),
|
|
1225
1247
|
useGitRootName,
|
|
1226
1248
|
showBranch,
|
|
1227
1249
|
showPath
|