unity-hub-cli 0.3.0 → 0.5.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 +324 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,22 +6,26 @@ import { render } from "ink";
|
|
|
6
6
|
|
|
7
7
|
// src/application/usecases.ts
|
|
8
8
|
var ListProjectsUseCase = class {
|
|
9
|
-
constructor(unityHubProjectsReader, gitRepositoryInfoReader, unityProjectOptionsReader) {
|
|
9
|
+
constructor(unityHubProjectsReader, gitRepositoryInfoReader, unityProjectOptionsReader, lockReader) {
|
|
10
10
|
this.unityHubProjectsReader = unityHubProjectsReader;
|
|
11
11
|
this.gitRepositoryInfoReader = gitRepositoryInfoReader;
|
|
12
12
|
this.unityProjectOptionsReader = unityProjectOptionsReader;
|
|
13
|
+
this.lockReader = lockReader;
|
|
13
14
|
}
|
|
14
15
|
async execute() {
|
|
15
16
|
const projects = await this.unityHubProjectsReader.listProjects();
|
|
16
|
-
const repositoryInfoResults = await Promise.
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const [repositoryInfoResults, lockResults] = await Promise.all([
|
|
18
|
+
Promise.allSettled(
|
|
19
|
+
projects.map((project) => this.gitRepositoryInfoReader.readRepositoryInfo(project.path))
|
|
20
|
+
),
|
|
21
|
+
Promise.allSettled(projects.map((project) => this.lockReader.isLocked(project.path)))
|
|
22
|
+
]);
|
|
19
23
|
return projects.map((project, index) => {
|
|
20
24
|
const repositoryResult = repositoryInfoResults[index];
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return { project };
|
|
25
|
+
const lockResult = lockResults[index];
|
|
26
|
+
const repository = repositoryResult.status === "fulfilled" ? repositoryResult.value ?? void 0 : void 0;
|
|
27
|
+
const isLocked = lockResult.status === "fulfilled" ? Boolean(lockResult.value) : false;
|
|
28
|
+
return { project, repository, isLocked };
|
|
25
29
|
});
|
|
26
30
|
}
|
|
27
31
|
};
|
|
@@ -53,6 +57,41 @@ var LaunchProjectUseCase = class {
|
|
|
53
57
|
await this.unityHubProjectsReader.updateLastModified(project.path, /* @__PURE__ */ new Date());
|
|
54
58
|
}
|
|
55
59
|
};
|
|
60
|
+
var TerminateProjectUseCase = class {
|
|
61
|
+
constructor(unityProcessReader, unityProcessTerminator, unityTempDirectoryCleaner) {
|
|
62
|
+
this.unityProcessReader = unityProcessReader;
|
|
63
|
+
this.unityProcessTerminator = unityProcessTerminator;
|
|
64
|
+
this.unityTempDirectoryCleaner = unityTempDirectoryCleaner;
|
|
65
|
+
}
|
|
66
|
+
async execute(project) {
|
|
67
|
+
const unityProcess = await this.unityProcessReader.findByProjectPath(project.path);
|
|
68
|
+
if (!unityProcess) {
|
|
69
|
+
return {
|
|
70
|
+
terminated: false,
|
|
71
|
+
message: "No Unity process is running for this project."
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const terminated = await this.unityProcessTerminator.terminate(unityProcess);
|
|
75
|
+
if (!terminated) {
|
|
76
|
+
return {
|
|
77
|
+
terminated: false,
|
|
78
|
+
message: "Failed to terminate the Unity process."
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
let cleanupMessage = void 0;
|
|
82
|
+
try {
|
|
83
|
+
await this.unityTempDirectoryCleaner.clean(project.path);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
console.error("Failed to clean Unity Temp directory:", message);
|
|
87
|
+
cleanupMessage = `Unity terminated, but failed to clean Temp: ${message}`;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
terminated: true,
|
|
91
|
+
message: cleanupMessage
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
56
95
|
|
|
57
96
|
// src/infrastructure/editor.ts
|
|
58
97
|
import { constants } from "fs";
|
|
@@ -66,7 +105,7 @@ var MacEditorPathResolver = class {
|
|
|
66
105
|
try {
|
|
67
106
|
await access(editorPath, constants.X_OK);
|
|
68
107
|
} catch {
|
|
69
|
-
throw new Error(
|
|
108
|
+
throw new Error(`Unity Editor not found for version ${version.value}.`);
|
|
70
109
|
}
|
|
71
110
|
return editorPath;
|
|
72
111
|
}
|
|
@@ -157,7 +196,7 @@ import { spawn } from "child_process";
|
|
|
157
196
|
var NodeProcessLauncher = class {
|
|
158
197
|
async launch(command, args, options) {
|
|
159
198
|
const detached = options?.detached ?? false;
|
|
160
|
-
await new Promise((
|
|
199
|
+
await new Promise((resolve3, reject) => {
|
|
161
200
|
const child = spawn(command, args, {
|
|
162
201
|
detached,
|
|
163
202
|
stdio: "ignore"
|
|
@@ -169,7 +208,7 @@ var NodeProcessLauncher = class {
|
|
|
169
208
|
const handleSpawn = () => {
|
|
170
209
|
child.off("error", handleError);
|
|
171
210
|
child.unref();
|
|
172
|
-
|
|
211
|
+
resolve3();
|
|
173
212
|
};
|
|
174
213
|
child.once("error", handleError);
|
|
175
214
|
child.once("spawn", handleSpawn);
|
|
@@ -230,17 +269,17 @@ var UnityHubProjectsReader = class {
|
|
|
230
269
|
content = await readFile2(HUB_PROJECTS_PATH, "utf8");
|
|
231
270
|
} catch {
|
|
232
271
|
throw new Error(
|
|
233
|
-
`Unity Hub
|
|
272
|
+
`Unity Hub project list not found (${HUB_PROJECTS_PATH}).`
|
|
234
273
|
);
|
|
235
274
|
}
|
|
236
275
|
let json;
|
|
237
276
|
try {
|
|
238
277
|
json = JSON.parse(content);
|
|
239
278
|
} catch {
|
|
240
|
-
throw new Error("Unity Hub
|
|
279
|
+
throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
|
|
241
280
|
}
|
|
242
281
|
if (json.schema_version && json.schema_version !== schemaVersion) {
|
|
243
|
-
throw new Error(
|
|
282
|
+
throw new Error(`Unsupported schema_version (${json.schema_version}).`);
|
|
244
283
|
}
|
|
245
284
|
const entries = Object.values(json.data ?? {});
|
|
246
285
|
if (entries.length === 0) {
|
|
@@ -255,14 +294,14 @@ var UnityHubProjectsReader = class {
|
|
|
255
294
|
content = await readFile2(HUB_PROJECTS_PATH, "utf8");
|
|
256
295
|
} catch {
|
|
257
296
|
throw new Error(
|
|
258
|
-
`Unity Hub
|
|
297
|
+
`Unity Hub project list not found (${HUB_PROJECTS_PATH}).`
|
|
259
298
|
);
|
|
260
299
|
}
|
|
261
300
|
let json;
|
|
262
301
|
try {
|
|
263
302
|
json = JSON.parse(content);
|
|
264
303
|
} catch {
|
|
265
|
-
throw new Error("Unity Hub
|
|
304
|
+
throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
|
|
266
305
|
}
|
|
267
306
|
if (!json.data) {
|
|
268
307
|
return;
|
|
@@ -365,7 +404,7 @@ var promptYesNoSingleKey = async () => {
|
|
|
365
404
|
const supportsRaw = isRawModeSupported();
|
|
366
405
|
const previousRaw = supportsRaw ? stdin.isRaw === true : false;
|
|
367
406
|
const wasPaused = stdin.isPaused();
|
|
368
|
-
return await new Promise((
|
|
407
|
+
return await new Promise((resolve3) => {
|
|
369
408
|
const cleanup = () => {
|
|
370
409
|
stdin.removeListener("data", handleData);
|
|
371
410
|
if (wasPaused) {
|
|
@@ -388,7 +427,7 @@ var promptYesNoSingleKey = async () => {
|
|
|
388
427
|
}
|
|
389
428
|
process.stdout.write("\n");
|
|
390
429
|
cleanup();
|
|
391
|
-
|
|
430
|
+
resolve3(result);
|
|
392
431
|
};
|
|
393
432
|
process.stdout.write(RAW_PROMPT_MESSAGE);
|
|
394
433
|
if (supportsRaw) {
|
|
@@ -406,9 +445,9 @@ var promptYesNoLine = async () => {
|
|
|
406
445
|
console.error("UnityLockfile exists. No interactive console available for confirmation.");
|
|
407
446
|
return false;
|
|
408
447
|
}
|
|
409
|
-
const confirmed = await new Promise((
|
|
448
|
+
const confirmed = await new Promise((resolve3) => {
|
|
410
449
|
prompt.rl.question(RAW_PROMPT_MESSAGE, (answer) => {
|
|
411
|
-
|
|
450
|
+
resolve3(answer.trim() === "y");
|
|
412
451
|
});
|
|
413
452
|
});
|
|
414
453
|
prompt.close();
|
|
@@ -441,6 +480,172 @@ var UnityLockChecker = class {
|
|
|
441
480
|
return "allow";
|
|
442
481
|
}
|
|
443
482
|
};
|
|
483
|
+
var UnityLockStatusReader = class {
|
|
484
|
+
async isLocked(projectPath) {
|
|
485
|
+
const lockfilePath = join3(projectPath, "Temp", "UnityLockfile");
|
|
486
|
+
return await pathExists(lockfilePath);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/infrastructure/unityProcess.ts
|
|
491
|
+
import { execFile } from "child_process";
|
|
492
|
+
import { resolve as resolve2 } from "path";
|
|
493
|
+
import { promisify } from "util";
|
|
494
|
+
var execFileAsync = promisify(execFile);
|
|
495
|
+
var UNITY_EXECUTABLE_PATTERN = /Unity\.app\/Contents\/MacOS\/Unity/i;
|
|
496
|
+
var PROJECT_PATH_PATTERN = /-(?:projectPath|projectpath)\s+("[^"]+"|'[^']+'|[^\s"']+)/i;
|
|
497
|
+
var PROCESS_LIST_ARGS = ["-axo", "pid=,command=", "-ww"];
|
|
498
|
+
var PROCESS_LIST_COMMAND = "ps";
|
|
499
|
+
var TERMINATE_TIMEOUT_MILLIS = 5e3;
|
|
500
|
+
var TERMINATE_POLL_INTERVAL_MILLIS = 200;
|
|
501
|
+
var delay = async (duration) => {
|
|
502
|
+
await new Promise((resolveDelay) => {
|
|
503
|
+
setTimeout(() => {
|
|
504
|
+
resolveDelay();
|
|
505
|
+
}, duration);
|
|
506
|
+
});
|
|
507
|
+
};
|
|
508
|
+
var normalizePath = (target) => {
|
|
509
|
+
const resolved = resolve2(target);
|
|
510
|
+
if (resolved.endsWith("/")) {
|
|
511
|
+
return resolved.slice(0, -1);
|
|
512
|
+
}
|
|
513
|
+
return resolved;
|
|
514
|
+
};
|
|
515
|
+
var arePathsEqual = (left, right) => {
|
|
516
|
+
const normalizedLeft = normalizePath(left);
|
|
517
|
+
const normalizedRight = normalizePath(right);
|
|
518
|
+
return normalizedLeft.localeCompare(normalizedRight, void 0, { sensitivity: "base" }) === 0;
|
|
519
|
+
};
|
|
520
|
+
var extractProjectPath = (command) => {
|
|
521
|
+
const match = command.match(PROJECT_PATH_PATTERN);
|
|
522
|
+
if (!match) {
|
|
523
|
+
return void 0;
|
|
524
|
+
}
|
|
525
|
+
const raw = match[1];
|
|
526
|
+
if (!raw) {
|
|
527
|
+
return void 0;
|
|
528
|
+
}
|
|
529
|
+
const trimmed = raw.trim();
|
|
530
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
531
|
+
return trimmed.slice(1, -1);
|
|
532
|
+
}
|
|
533
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
534
|
+
return trimmed.slice(1, -1);
|
|
535
|
+
}
|
|
536
|
+
return trimmed;
|
|
537
|
+
};
|
|
538
|
+
var isUnityMainProcess = (command) => {
|
|
539
|
+
return UNITY_EXECUTABLE_PATTERN.test(command);
|
|
540
|
+
};
|
|
541
|
+
var isProcessMissingError = (error) => {
|
|
542
|
+
if (typeof error !== "object" || error === null) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
const nodeError = error;
|
|
546
|
+
return nodeError.code === "ESRCH";
|
|
547
|
+
};
|
|
548
|
+
var ensureProcessAlive = (pid) => {
|
|
549
|
+
try {
|
|
550
|
+
process.kill(pid, 0);
|
|
551
|
+
return true;
|
|
552
|
+
} catch (error) {
|
|
553
|
+
if (isProcessMissingError(error)) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
var MacUnityProcessReader = class {
|
|
560
|
+
async findByProjectPath(projectPath) {
|
|
561
|
+
const normalizedTarget = normalizePath(projectPath);
|
|
562
|
+
const processes = await this.listUnityProcesses();
|
|
563
|
+
return processes.find((candidate) => arePathsEqual(candidate.projectPath, normalizedTarget));
|
|
564
|
+
}
|
|
565
|
+
async listUnityProcesses() {
|
|
566
|
+
let stdout;
|
|
567
|
+
try {
|
|
568
|
+
const result = await execFileAsync(PROCESS_LIST_COMMAND, PROCESS_LIST_ARGS);
|
|
569
|
+
stdout = result.stdout;
|
|
570
|
+
} catch (error) {
|
|
571
|
+
throw new Error(`Failed to retrieve Unity process list: ${error instanceof Error ? error.message : String(error)}`);
|
|
572
|
+
}
|
|
573
|
+
const lines = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
574
|
+
return lines.map((line) => {
|
|
575
|
+
const match = line.match(/^(\d+)\s+(.*)$/);
|
|
576
|
+
if (!match) {
|
|
577
|
+
return void 0;
|
|
578
|
+
}
|
|
579
|
+
const pidValue = Number.parseInt(match[1] ?? "", 10);
|
|
580
|
+
if (!Number.isFinite(pidValue)) {
|
|
581
|
+
return void 0;
|
|
582
|
+
}
|
|
583
|
+
const command = match[2] ?? "";
|
|
584
|
+
if (!isUnityMainProcess(command)) {
|
|
585
|
+
return void 0;
|
|
586
|
+
}
|
|
587
|
+
const projectArgument = extractProjectPath(command);
|
|
588
|
+
if (!projectArgument) {
|
|
589
|
+
return void 0;
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
pid: pidValue,
|
|
593
|
+
projectPath: normalizePath(projectArgument)
|
|
594
|
+
};
|
|
595
|
+
}).filter((process3) => Boolean(process3));
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
var MacUnityProcessTerminator = class {
|
|
599
|
+
async terminate(unityProcess) {
|
|
600
|
+
try {
|
|
601
|
+
process.kill(unityProcess.pid, "SIGTERM");
|
|
602
|
+
} catch (error) {
|
|
603
|
+
if (isProcessMissingError(error)) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
throw new Error(
|
|
607
|
+
`Failed to terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const deadline = Date.now() + TERMINATE_TIMEOUT_MILLIS;
|
|
611
|
+
while (Date.now() < deadline) {
|
|
612
|
+
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
613
|
+
const alive = ensureProcessAlive(unityProcess.pid);
|
|
614
|
+
if (!alive) {
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
process.kill(unityProcess.pid, "SIGKILL");
|
|
620
|
+
} catch (error) {
|
|
621
|
+
if (isProcessMissingError(error)) {
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
throw new Error(
|
|
625
|
+
`Failed to forcefully terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
629
|
+
return !ensureProcessAlive(unityProcess.pid);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// src/infrastructure/unityTemp.ts
|
|
634
|
+
import { rm as rm2 } from "fs/promises";
|
|
635
|
+
import { join as join4 } from "path";
|
|
636
|
+
var TEMP_DIRECTORY_NAME = "Temp";
|
|
637
|
+
var UnityTempDirectoryCleaner = class {
|
|
638
|
+
async clean(projectPath) {
|
|
639
|
+
const tempDirectoryPath = join4(projectPath, TEMP_DIRECTORY_NAME);
|
|
640
|
+
try {
|
|
641
|
+
await rm2(tempDirectoryPath, {
|
|
642
|
+
recursive: true,
|
|
643
|
+
force: true
|
|
644
|
+
});
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
444
649
|
|
|
445
650
|
// src/presentation/App.tsx
|
|
446
651
|
import clipboard from "clipboardy";
|
|
@@ -530,10 +735,12 @@ var formatUpdatedText = (lastModified) => {
|
|
|
530
735
|
var homeDirectory = process.env.HOME ?? "";
|
|
531
736
|
var homePrefix = homeDirectory ? `${homeDirectory}/` : "";
|
|
532
737
|
var minimumVisibleProjectCount = 4;
|
|
533
|
-
var defaultHintMessage = "Move with arrows or j/k \xB7 Launch with o \xB7
|
|
738
|
+
var defaultHintMessage = "Move with arrows or j/k \xB7 Launch with o \xB7 Quit Unity with q \xB7 Copy cd path with c";
|
|
534
739
|
var PROJECT_COLOR = "#abd8e7";
|
|
535
740
|
var BRANCH_COLOR = "#e3839c";
|
|
536
741
|
var PATH_COLOR = "#719bd8";
|
|
742
|
+
var LOCK_COLOR = "yellow";
|
|
743
|
+
var LOCK_LABEL = "[running]";
|
|
537
744
|
var shortenHomePath = (targetPath) => {
|
|
538
745
|
if (!homeDirectory) {
|
|
539
746
|
return targetPath;
|
|
@@ -553,6 +760,7 @@ var buildCdCommand = (targetPath) => {
|
|
|
553
760
|
var App = ({
|
|
554
761
|
projects,
|
|
555
762
|
onLaunch,
|
|
763
|
+
onTerminate,
|
|
556
764
|
useGitRootName = true,
|
|
557
765
|
showBranch = true,
|
|
558
766
|
showPath = true
|
|
@@ -563,6 +771,8 @@ var App = ({
|
|
|
563
771
|
const [index, setIndex] = useState(0);
|
|
564
772
|
const [hint, setHint] = useState(defaultHintMessage);
|
|
565
773
|
const [windowStart, setWindowStart] = useState(0);
|
|
774
|
+
const [releasedProjects, setReleasedProjects] = useState(/* @__PURE__ */ new Set());
|
|
775
|
+
const [launchedProjects, setLaunchedProjects] = useState(/* @__PURE__ */ new Set());
|
|
566
776
|
const linesPerProject = (showBranch ? 1 : 0) + (showPath ? 1 : 0) + 2;
|
|
567
777
|
const sortedProjects = useMemo(() => {
|
|
568
778
|
const fallbackTime = 0;
|
|
@@ -736,6 +946,19 @@ var App = ({
|
|
|
736
946
|
}
|
|
737
947
|
try {
|
|
738
948
|
await onLaunch(project);
|
|
949
|
+
setLaunchedProjects((previous) => {
|
|
950
|
+
const next = new Set(previous);
|
|
951
|
+
next.add(project.id);
|
|
952
|
+
return next;
|
|
953
|
+
});
|
|
954
|
+
setReleasedProjects((previous) => {
|
|
955
|
+
if (!previous.has(project.id)) {
|
|
956
|
+
return previous;
|
|
957
|
+
}
|
|
958
|
+
const next = new Set(previous);
|
|
959
|
+
next.delete(project.id);
|
|
960
|
+
return next;
|
|
961
|
+
});
|
|
739
962
|
setHint(`Launched: ${project.title}`);
|
|
740
963
|
setTimeout(() => {
|
|
741
964
|
setHint(defaultHintMessage);
|
|
@@ -755,6 +978,53 @@ var App = ({
|
|
|
755
978
|
}, 3e3);
|
|
756
979
|
}
|
|
757
980
|
}, [index, onLaunch, sortedProjects]);
|
|
981
|
+
const terminateSelected = useCallback(async () => {
|
|
982
|
+
const projectView = sortedProjects[index];
|
|
983
|
+
if (!projectView) {
|
|
984
|
+
setHint("No project to terminate");
|
|
985
|
+
setTimeout(() => {
|
|
986
|
+
setHint(defaultHintMessage);
|
|
987
|
+
}, 2e3);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
try {
|
|
991
|
+
const result = await onTerminate(projectView.project);
|
|
992
|
+
if (!result.terminated) {
|
|
993
|
+
setHint(result.message ?? "No running Unity for this project");
|
|
994
|
+
setTimeout(() => {
|
|
995
|
+
setHint(defaultHintMessage);
|
|
996
|
+
}, 3e3);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
setHint(`Stopped Unity: ${projectView.project.title}`);
|
|
1000
|
+
setTimeout(() => {
|
|
1001
|
+
setHint(defaultHintMessage);
|
|
1002
|
+
}, 3e3);
|
|
1003
|
+
setLaunchedProjects((previous) => {
|
|
1004
|
+
if (!previous.has(projectView.project.id)) {
|
|
1005
|
+
return previous;
|
|
1006
|
+
}
|
|
1007
|
+
const next = new Set(previous);
|
|
1008
|
+
next.delete(projectView.project.id);
|
|
1009
|
+
return next;
|
|
1010
|
+
});
|
|
1011
|
+
setReleasedProjects((previous) => {
|
|
1012
|
+
const next = new Set(previous);
|
|
1013
|
+
next.add(projectView.project.id);
|
|
1014
|
+
return next;
|
|
1015
|
+
});
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1018
|
+
setHint(`Failed to stop: ${message}`);
|
|
1019
|
+
setTimeout(() => {
|
|
1020
|
+
setHint(defaultHintMessage);
|
|
1021
|
+
}, 3e3);
|
|
1022
|
+
}
|
|
1023
|
+
}, [index, onTerminate, sortedProjects]);
|
|
1024
|
+
useEffect(() => {
|
|
1025
|
+
setReleasedProjects(/* @__PURE__ */ new Set());
|
|
1026
|
+
setLaunchedProjects(/* @__PURE__ */ new Set());
|
|
1027
|
+
}, [projects]);
|
|
758
1028
|
useInput((input, key) => {
|
|
759
1029
|
if (input === "j" || key.downArrow) {
|
|
760
1030
|
move(1);
|
|
@@ -762,8 +1032,13 @@ var App = ({
|
|
|
762
1032
|
if (input === "k" || key.upArrow) {
|
|
763
1033
|
move(-1);
|
|
764
1034
|
}
|
|
1035
|
+
if (input === "q") {
|
|
1036
|
+
void terminateSelected();
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
765
1039
|
if (input === "o") {
|
|
766
1040
|
void launchSelected();
|
|
1041
|
+
return;
|
|
767
1042
|
}
|
|
768
1043
|
if (input === "c") {
|
|
769
1044
|
copyProjectPath();
|
|
@@ -812,7 +1087,7 @@ var App = ({
|
|
|
812
1087
|
});
|
|
813
1088
|
}, [linesPerProject, projects.length, startIndex, visibleProjects]);
|
|
814
1089
|
const rows = useMemo(() => {
|
|
815
|
-
return visibleProjects.map(({ project, repository }, offset) => {
|
|
1090
|
+
return visibleProjects.map(({ project, repository, isLocked }, offset) => {
|
|
816
1091
|
const rowIndex = startIndex + offset;
|
|
817
1092
|
const isSelected = rowIndex === index;
|
|
818
1093
|
const arrow = isSelected ? ">" : " ";
|
|
@@ -821,6 +1096,7 @@ var App = ({
|
|
|
821
1096
|
const updatedText = formatUpdatedText(project.lastModified);
|
|
822
1097
|
const pathLine = shortenHomePath(project.path);
|
|
823
1098
|
const branchLine = formatBranch(repository?.branch);
|
|
1099
|
+
const activeLock = isLocked && !releasedProjects.has(project.id) || launchedProjects.has(project.id);
|
|
824
1100
|
const baseScrollbarIndex = offset * linesPerProject;
|
|
825
1101
|
const titleScrollbar = scrollbarChars[baseScrollbarIndex] ?? " ";
|
|
826
1102
|
const branchScrollbar = showBranch ? scrollbarChars[baseScrollbarIndex + 1] ?? " " : " ";
|
|
@@ -838,7 +1114,8 @@ var App = ({
|
|
|
838
1114
|
" ",
|
|
839
1115
|
versionLabel
|
|
840
1116
|
] }),
|
|
841
|
-
updatedText ? /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: ` ${updatedText}` }) : null
|
|
1117
|
+
updatedText ? /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: ` ${updatedText}` }) : null,
|
|
1118
|
+
activeLock ? /* @__PURE__ */ jsx(Text, { color: LOCK_COLOR, children: ` ${LOCK_LABEL}` }) : null
|
|
842
1119
|
] }),
|
|
843
1120
|
showBranch ? /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : BRANCH_COLOR, children: [
|
|
844
1121
|
" ",
|
|
@@ -858,9 +1135,19 @@ var App = ({
|
|
|
858
1135
|
] })
|
|
859
1136
|
] }, project.id);
|
|
860
1137
|
});
|
|
861
|
-
}, [
|
|
1138
|
+
}, [
|
|
1139
|
+
index,
|
|
1140
|
+
launchedProjects,
|
|
1141
|
+
releasedProjects,
|
|
1142
|
+
scrollbarChars,
|
|
1143
|
+
showBranch,
|
|
1144
|
+
showPath,
|
|
1145
|
+
startIndex,
|
|
1146
|
+
useGitRootName,
|
|
1147
|
+
visibleProjects
|
|
1148
|
+
]);
|
|
862
1149
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
863
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", children: rows.length === 0 ? /* @__PURE__ */ jsx(Text, { children: "Unity Hub
|
|
1150
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", children: rows.length === 0 ? /* @__PURE__ */ jsx(Text, { children: "No Unity Hub projects were found." }) : rows }),
|
|
864
1151
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { children: hint }) })
|
|
865
1152
|
] });
|
|
866
1153
|
};
|
|
@@ -870,14 +1157,19 @@ import { jsx as jsx2 } from "react/jsx-runtime";
|
|
|
870
1157
|
var bootstrap = async () => {
|
|
871
1158
|
const unityHubReader = new UnityHubProjectsReader();
|
|
872
1159
|
const gitRepositoryInfoReader = new GitRepositoryInfoReader();
|
|
1160
|
+
const lockStatusReader = new UnityLockStatusReader();
|
|
873
1161
|
const listProjectsUseCase = new ListProjectsUseCase(
|
|
874
1162
|
unityHubReader,
|
|
875
1163
|
gitRepositoryInfoReader,
|
|
876
|
-
unityHubReader
|
|
1164
|
+
unityHubReader,
|
|
1165
|
+
lockStatusReader
|
|
877
1166
|
);
|
|
878
1167
|
const editorPathResolver = new MacEditorPathResolver();
|
|
879
1168
|
const processLauncher = new NodeProcessLauncher();
|
|
880
1169
|
const lockChecker = new UnityLockChecker();
|
|
1170
|
+
const unityProcessReader = new MacUnityProcessReader();
|
|
1171
|
+
const unityProcessTerminator = new MacUnityProcessTerminator();
|
|
1172
|
+
const unityTempDirectoryCleaner = new UnityTempDirectoryCleaner();
|
|
881
1173
|
const launchProjectUseCase = new LaunchProjectUseCase(
|
|
882
1174
|
editorPathResolver,
|
|
883
1175
|
processLauncher,
|
|
@@ -885,6 +1177,11 @@ var bootstrap = async () => {
|
|
|
885
1177
|
unityHubReader,
|
|
886
1178
|
lockChecker
|
|
887
1179
|
);
|
|
1180
|
+
const terminateProjectUseCase = new TerminateProjectUseCase(
|
|
1181
|
+
unityProcessReader,
|
|
1182
|
+
unityProcessTerminator,
|
|
1183
|
+
unityTempDirectoryCleaner
|
|
1184
|
+
);
|
|
888
1185
|
const useGitRootName = !process2.argv.includes("--no-git-root-name");
|
|
889
1186
|
const showBranch = !process2.argv.includes("--hide-branch");
|
|
890
1187
|
const showPath = !process2.argv.includes("--hide-path");
|
|
@@ -896,6 +1193,7 @@ var bootstrap = async () => {
|
|
|
896
1193
|
{
|
|
897
1194
|
projects,
|
|
898
1195
|
onLaunch: (project) => launchProjectUseCase.execute(project),
|
|
1196
|
+
onTerminate: (project) => terminateProjectUseCase.execute(project),
|
|
899
1197
|
useGitRootName,
|
|
900
1198
|
showBranch,
|
|
901
1199
|
showPath
|