unity-hub-cli 0.4.0 → 0.6.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/README.md +2 -0
- package/dist/index.js +329 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
A CLI tool that displays the same content as Unity Hub in an Ink-based TUI, allows navigation with arrow keys/`j`/`k`, and launches Unity Editor by pressing `o`.
|
|
6
6
|
|
|
7
|
+
<img width="1678" height="1460" alt="スクリーンショット 2025-10-27 23 44 40" src="https://github.com/user-attachments/assets/db3cc995-820e-490b-a43b-393893197ab4" />
|
|
8
|
+
|
|
7
9
|
## Requirements
|
|
8
10
|
|
|
9
11
|
- macOS
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,41 @@ var LaunchProjectUseCase = class {
|
|
|
57
57
|
await this.unityHubProjectsReader.updateLastModified(project.path, /* @__PURE__ */ new Date());
|
|
58
58
|
}
|
|
59
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
|
+
};
|
|
60
95
|
|
|
61
96
|
// src/infrastructure/editor.ts
|
|
62
97
|
import { constants } from "fs";
|
|
@@ -70,7 +105,7 @@ var MacEditorPathResolver = class {
|
|
|
70
105
|
try {
|
|
71
106
|
await access(editorPath, constants.X_OK);
|
|
72
107
|
} catch {
|
|
73
|
-
throw new Error(
|
|
108
|
+
throw new Error(`Unity Editor not found for version ${version.value}.`);
|
|
74
109
|
}
|
|
75
110
|
return editorPath;
|
|
76
111
|
}
|
|
@@ -161,7 +196,7 @@ import { spawn } from "child_process";
|
|
|
161
196
|
var NodeProcessLauncher = class {
|
|
162
197
|
async launch(command, args, options) {
|
|
163
198
|
const detached = options?.detached ?? false;
|
|
164
|
-
await new Promise((
|
|
199
|
+
await new Promise((resolve3, reject) => {
|
|
165
200
|
const child = spawn(command, args, {
|
|
166
201
|
detached,
|
|
167
202
|
stdio: "ignore"
|
|
@@ -173,7 +208,7 @@ var NodeProcessLauncher = class {
|
|
|
173
208
|
const handleSpawn = () => {
|
|
174
209
|
child.off("error", handleError);
|
|
175
210
|
child.unref();
|
|
176
|
-
|
|
211
|
+
resolve3();
|
|
177
212
|
};
|
|
178
213
|
child.once("error", handleError);
|
|
179
214
|
child.once("spawn", handleSpawn);
|
|
@@ -234,17 +269,17 @@ var UnityHubProjectsReader = class {
|
|
|
234
269
|
content = await readFile2(HUB_PROJECTS_PATH, "utf8");
|
|
235
270
|
} catch {
|
|
236
271
|
throw new Error(
|
|
237
|
-
`Unity Hub
|
|
272
|
+
`Unity Hub project list not found (${HUB_PROJECTS_PATH}).`
|
|
238
273
|
);
|
|
239
274
|
}
|
|
240
275
|
let json;
|
|
241
276
|
try {
|
|
242
277
|
json = JSON.parse(content);
|
|
243
278
|
} catch {
|
|
244
|
-
throw new Error("Unity Hub
|
|
279
|
+
throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
|
|
245
280
|
}
|
|
246
281
|
if (json.schema_version && json.schema_version !== schemaVersion) {
|
|
247
|
-
throw new Error(
|
|
282
|
+
throw new Error(`Unsupported schema_version (${json.schema_version}).`);
|
|
248
283
|
}
|
|
249
284
|
const entries = Object.values(json.data ?? {});
|
|
250
285
|
if (entries.length === 0) {
|
|
@@ -259,14 +294,14 @@ var UnityHubProjectsReader = class {
|
|
|
259
294
|
content = await readFile2(HUB_PROJECTS_PATH, "utf8");
|
|
260
295
|
} catch {
|
|
261
296
|
throw new Error(
|
|
262
|
-
`Unity Hub
|
|
297
|
+
`Unity Hub project list not found (${HUB_PROJECTS_PATH}).`
|
|
263
298
|
);
|
|
264
299
|
}
|
|
265
300
|
let json;
|
|
266
301
|
try {
|
|
267
302
|
json = JSON.parse(content);
|
|
268
303
|
} catch {
|
|
269
|
-
throw new Error("Unity Hub
|
|
304
|
+
throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
|
|
270
305
|
}
|
|
271
306
|
if (!json.data) {
|
|
272
307
|
return;
|
|
@@ -312,11 +347,17 @@ var UnityHubProjectsReader = class {
|
|
|
312
347
|
};
|
|
313
348
|
|
|
314
349
|
// src/infrastructure/unityLock.ts
|
|
350
|
+
import { execFile } from "child_process";
|
|
315
351
|
import { constants as constants2, createReadStream, createWriteStream } from "fs";
|
|
316
352
|
import { access as access2, rm } from "fs/promises";
|
|
317
353
|
import { join as join3 } from "path";
|
|
318
354
|
import readline from "readline";
|
|
355
|
+
import { promisify } from "util";
|
|
319
356
|
var RAW_PROMPT_MESSAGE = "Delete UnityLockfile and continue? Type 'y' to continue; anything else aborts: ";
|
|
357
|
+
var execFileAsync = promisify(execFile);
|
|
358
|
+
var buildBringToFrontScript = (pid) => {
|
|
359
|
+
return `tell application "System Events" to set frontmost of (first process whose unix id is ${pid}) to true`;
|
|
360
|
+
};
|
|
320
361
|
var isRawModeSupported = () => {
|
|
321
362
|
const stdin = process.stdin;
|
|
322
363
|
return Boolean(stdin?.isTTY && typeof stdin.setRawMode === "function" && process.stdout.isTTY);
|
|
@@ -369,7 +410,7 @@ var promptYesNoSingleKey = async () => {
|
|
|
369
410
|
const supportsRaw = isRawModeSupported();
|
|
370
411
|
const previousRaw = supportsRaw ? stdin.isRaw === true : false;
|
|
371
412
|
const wasPaused = stdin.isPaused();
|
|
372
|
-
return await new Promise((
|
|
413
|
+
return await new Promise((resolve3) => {
|
|
373
414
|
const cleanup = () => {
|
|
374
415
|
stdin.removeListener("data", handleData);
|
|
375
416
|
if (wasPaused) {
|
|
@@ -392,7 +433,7 @@ var promptYesNoSingleKey = async () => {
|
|
|
392
433
|
}
|
|
393
434
|
process.stdout.write("\n");
|
|
394
435
|
cleanup();
|
|
395
|
-
|
|
436
|
+
resolve3(result);
|
|
396
437
|
};
|
|
397
438
|
process.stdout.write(RAW_PROMPT_MESSAGE);
|
|
398
439
|
if (supportsRaw) {
|
|
@@ -410,9 +451,9 @@ var promptYesNoLine = async () => {
|
|
|
410
451
|
console.error("UnityLockfile exists. No interactive console available for confirmation.");
|
|
411
452
|
return false;
|
|
412
453
|
}
|
|
413
|
-
const confirmed = await new Promise((
|
|
454
|
+
const confirmed = await new Promise((resolve3) => {
|
|
414
455
|
prompt.rl.question(RAW_PROMPT_MESSAGE, (answer) => {
|
|
415
|
-
|
|
456
|
+
resolve3(answer.trim() === "y");
|
|
416
457
|
});
|
|
417
458
|
});
|
|
418
459
|
prompt.close();
|
|
@@ -427,7 +468,18 @@ var pathExists = async (target) => {
|
|
|
427
468
|
}
|
|
428
469
|
};
|
|
429
470
|
var UnityLockChecker = class {
|
|
471
|
+
constructor(unityProcessReader) {
|
|
472
|
+
this.unityProcessReader = unityProcessReader;
|
|
473
|
+
}
|
|
430
474
|
async check(projectPath) {
|
|
475
|
+
const activeProcess = await this.unityProcessReader.findByProjectPath(projectPath);
|
|
476
|
+
if (activeProcess) {
|
|
477
|
+
console.log(
|
|
478
|
+
`Unity process already running for project: ${activeProcess.projectPath} (PID: ${activeProcess.pid})`
|
|
479
|
+
);
|
|
480
|
+
await this.bringUnityToFront(activeProcess.pid);
|
|
481
|
+
return "skip";
|
|
482
|
+
}
|
|
431
483
|
const lockfilePath = join3(projectPath, "Temp", "UnityLockfile");
|
|
432
484
|
const hasLockfile = await pathExists(lockfilePath);
|
|
433
485
|
if (!hasLockfile) {
|
|
@@ -444,6 +496,18 @@ var UnityLockChecker = class {
|
|
|
444
496
|
console.log("Deleted UnityLockfile. Continuing launch.");
|
|
445
497
|
return "allow";
|
|
446
498
|
}
|
|
499
|
+
async bringUnityToFront(pid) {
|
|
500
|
+
if (process.platform !== "darwin") {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
const script = buildBringToFrontScript(pid);
|
|
505
|
+
await execFileAsync("osascript", ["-e", script]);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
508
|
+
console.error(`Failed to bring Unity to front: ${message}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
447
511
|
};
|
|
448
512
|
var UnityLockStatusReader = class {
|
|
449
513
|
async isLocked(projectPath) {
|
|
@@ -452,6 +516,166 @@ var UnityLockStatusReader = class {
|
|
|
452
516
|
}
|
|
453
517
|
};
|
|
454
518
|
|
|
519
|
+
// src/infrastructure/unityProcess.ts
|
|
520
|
+
import { execFile as execFile2 } from "child_process";
|
|
521
|
+
import { resolve as resolve2 } from "path";
|
|
522
|
+
import { promisify as promisify2 } from "util";
|
|
523
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
524
|
+
var UNITY_EXECUTABLE_PATTERN = /Unity\.app\/Contents\/MacOS\/Unity/i;
|
|
525
|
+
var PROJECT_PATH_PATTERN = /-(?:projectPath|projectpath)(?:=|\s+)("[^"]+"|'[^']+'|[^\s"']+)/i;
|
|
526
|
+
var PROCESS_LIST_ARGS = ["-axo", "pid=,command=", "-ww"];
|
|
527
|
+
var PROCESS_LIST_COMMAND = "ps";
|
|
528
|
+
var TERMINATE_TIMEOUT_MILLIS = 5e3;
|
|
529
|
+
var TERMINATE_POLL_INTERVAL_MILLIS = 200;
|
|
530
|
+
var delay = async (duration) => {
|
|
531
|
+
await new Promise((resolveDelay) => {
|
|
532
|
+
setTimeout(() => {
|
|
533
|
+
resolveDelay();
|
|
534
|
+
}, duration);
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
var normalizePath = (target) => {
|
|
538
|
+
const resolved = resolve2(target);
|
|
539
|
+
if (resolved.endsWith("/")) {
|
|
540
|
+
return resolved.slice(0, -1);
|
|
541
|
+
}
|
|
542
|
+
return resolved;
|
|
543
|
+
};
|
|
544
|
+
var arePathsEqual = (left, right) => {
|
|
545
|
+
const normalizedLeft = normalizePath(left);
|
|
546
|
+
const normalizedRight = normalizePath(right);
|
|
547
|
+
return normalizedLeft.localeCompare(normalizedRight, void 0, { sensitivity: "base" }) === 0;
|
|
548
|
+
};
|
|
549
|
+
var extractProjectPath = (command) => {
|
|
550
|
+
const match = command.match(PROJECT_PATH_PATTERN);
|
|
551
|
+
if (!match) {
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
const raw = match[1];
|
|
555
|
+
if (!raw) {
|
|
556
|
+
return void 0;
|
|
557
|
+
}
|
|
558
|
+
const trimmed = raw.trim();
|
|
559
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
560
|
+
return trimmed.slice(1, -1);
|
|
561
|
+
}
|
|
562
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
563
|
+
return trimmed.slice(1, -1);
|
|
564
|
+
}
|
|
565
|
+
return trimmed;
|
|
566
|
+
};
|
|
567
|
+
var isUnityMainProcess = (command) => {
|
|
568
|
+
return UNITY_EXECUTABLE_PATTERN.test(command);
|
|
569
|
+
};
|
|
570
|
+
var isProcessMissingError = (error) => {
|
|
571
|
+
if (typeof error !== "object" || error === null) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
const nodeError = error;
|
|
575
|
+
return nodeError.code === "ESRCH";
|
|
576
|
+
};
|
|
577
|
+
var ensureProcessAlive = (pid) => {
|
|
578
|
+
try {
|
|
579
|
+
process.kill(pid, 0);
|
|
580
|
+
return true;
|
|
581
|
+
} catch (error) {
|
|
582
|
+
if (isProcessMissingError(error)) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
throw error;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
var MacUnityProcessReader = class {
|
|
589
|
+
async findByProjectPath(projectPath) {
|
|
590
|
+
const normalizedTarget = normalizePath(projectPath);
|
|
591
|
+
const processes = await this.listUnityProcesses();
|
|
592
|
+
return processes.find((candidate) => arePathsEqual(candidate.projectPath, normalizedTarget));
|
|
593
|
+
}
|
|
594
|
+
async listUnityProcesses() {
|
|
595
|
+
let stdout;
|
|
596
|
+
try {
|
|
597
|
+
const result = await execFileAsync2(PROCESS_LIST_COMMAND, PROCESS_LIST_ARGS);
|
|
598
|
+
stdout = result.stdout;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
throw new Error(`Failed to retrieve Unity process list: ${error instanceof Error ? error.message : String(error)}`);
|
|
601
|
+
}
|
|
602
|
+
const lines = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
603
|
+
return lines.map((line) => {
|
|
604
|
+
const match = line.match(/^(\d+)\s+(.*)$/);
|
|
605
|
+
if (!match) {
|
|
606
|
+
return void 0;
|
|
607
|
+
}
|
|
608
|
+
const pidValue = Number.parseInt(match[1] ?? "", 10);
|
|
609
|
+
if (!Number.isFinite(pidValue)) {
|
|
610
|
+
return void 0;
|
|
611
|
+
}
|
|
612
|
+
const command = match[2] ?? "";
|
|
613
|
+
if (!isUnityMainProcess(command)) {
|
|
614
|
+
return void 0;
|
|
615
|
+
}
|
|
616
|
+
const projectArgument = extractProjectPath(command);
|
|
617
|
+
if (!projectArgument) {
|
|
618
|
+
return void 0;
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
pid: pidValue,
|
|
622
|
+
projectPath: normalizePath(projectArgument)
|
|
623
|
+
};
|
|
624
|
+
}).filter((process3) => Boolean(process3));
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
var MacUnityProcessTerminator = class {
|
|
628
|
+
async terminate(unityProcess) {
|
|
629
|
+
try {
|
|
630
|
+
process.kill(unityProcess.pid, "SIGTERM");
|
|
631
|
+
} catch (error) {
|
|
632
|
+
if (isProcessMissingError(error)) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
throw new Error(
|
|
636
|
+
`Failed to terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
const deadline = Date.now() + TERMINATE_TIMEOUT_MILLIS;
|
|
640
|
+
while (Date.now() < deadline) {
|
|
641
|
+
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
642
|
+
const alive = ensureProcessAlive(unityProcess.pid);
|
|
643
|
+
if (!alive) {
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
process.kill(unityProcess.pid, "SIGKILL");
|
|
649
|
+
} catch (error) {
|
|
650
|
+
if (isProcessMissingError(error)) {
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
throw new Error(
|
|
654
|
+
`Failed to forcefully terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
658
|
+
return !ensureProcessAlive(unityProcess.pid);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
// src/infrastructure/unityTemp.ts
|
|
663
|
+
import { rm as rm2 } from "fs/promises";
|
|
664
|
+
import { join as join4 } from "path";
|
|
665
|
+
var TEMP_DIRECTORY_NAME = "Temp";
|
|
666
|
+
var UnityTempDirectoryCleaner = class {
|
|
667
|
+
async clean(projectPath) {
|
|
668
|
+
const tempDirectoryPath = join4(projectPath, TEMP_DIRECTORY_NAME);
|
|
669
|
+
try {
|
|
670
|
+
await rm2(tempDirectoryPath, {
|
|
671
|
+
recursive: true,
|
|
672
|
+
force: true
|
|
673
|
+
});
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
|
|
455
679
|
// src/presentation/App.tsx
|
|
456
680
|
import clipboard from "clipboardy";
|
|
457
681
|
import { Box, Text, useApp, useInput, useStdout } from "ink";
|
|
@@ -540,7 +764,7 @@ var formatUpdatedText = (lastModified) => {
|
|
|
540
764
|
var homeDirectory = process.env.HOME ?? "";
|
|
541
765
|
var homePrefix = homeDirectory ? `${homeDirectory}/` : "";
|
|
542
766
|
var minimumVisibleProjectCount = 4;
|
|
543
|
-
var defaultHintMessage = "Move with arrows or j/k \xB7 Launch with o \xB7
|
|
767
|
+
var defaultHintMessage = "Move with arrows or j/k \xB7 Launch with o \xB7 Quit Unity with q \xB7 Copy cd path with c";
|
|
544
768
|
var PROJECT_COLOR = "#abd8e7";
|
|
545
769
|
var BRANCH_COLOR = "#e3839c";
|
|
546
770
|
var PATH_COLOR = "#719bd8";
|
|
@@ -565,6 +789,7 @@ var buildCdCommand = (targetPath) => {
|
|
|
565
789
|
var App = ({
|
|
566
790
|
projects,
|
|
567
791
|
onLaunch,
|
|
792
|
+
onTerminate,
|
|
568
793
|
useGitRootName = true,
|
|
569
794
|
showBranch = true,
|
|
570
795
|
showPath = true
|
|
@@ -575,6 +800,8 @@ var App = ({
|
|
|
575
800
|
const [index, setIndex] = useState(0);
|
|
576
801
|
const [hint, setHint] = useState(defaultHintMessage);
|
|
577
802
|
const [windowStart, setWindowStart] = useState(0);
|
|
803
|
+
const [releasedProjects, setReleasedProjects] = useState(/* @__PURE__ */ new Set());
|
|
804
|
+
const [launchedProjects, setLaunchedProjects] = useState(/* @__PURE__ */ new Set());
|
|
578
805
|
const linesPerProject = (showBranch ? 1 : 0) + (showPath ? 1 : 0) + 2;
|
|
579
806
|
const sortedProjects = useMemo(() => {
|
|
580
807
|
const fallbackTime = 0;
|
|
@@ -748,6 +975,19 @@ var App = ({
|
|
|
748
975
|
}
|
|
749
976
|
try {
|
|
750
977
|
await onLaunch(project);
|
|
978
|
+
setLaunchedProjects((previous) => {
|
|
979
|
+
const next = new Set(previous);
|
|
980
|
+
next.add(project.id);
|
|
981
|
+
return next;
|
|
982
|
+
});
|
|
983
|
+
setReleasedProjects((previous) => {
|
|
984
|
+
if (!previous.has(project.id)) {
|
|
985
|
+
return previous;
|
|
986
|
+
}
|
|
987
|
+
const next = new Set(previous);
|
|
988
|
+
next.delete(project.id);
|
|
989
|
+
return next;
|
|
990
|
+
});
|
|
751
991
|
setHint(`Launched: ${project.title}`);
|
|
752
992
|
setTimeout(() => {
|
|
753
993
|
setHint(defaultHintMessage);
|
|
@@ -767,6 +1007,53 @@ var App = ({
|
|
|
767
1007
|
}, 3e3);
|
|
768
1008
|
}
|
|
769
1009
|
}, [index, onLaunch, sortedProjects]);
|
|
1010
|
+
const terminateSelected = useCallback(async () => {
|
|
1011
|
+
const projectView = sortedProjects[index];
|
|
1012
|
+
if (!projectView) {
|
|
1013
|
+
setHint("No project to terminate");
|
|
1014
|
+
setTimeout(() => {
|
|
1015
|
+
setHint(defaultHintMessage);
|
|
1016
|
+
}, 2e3);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
const result = await onTerminate(projectView.project);
|
|
1021
|
+
if (!result.terminated) {
|
|
1022
|
+
setHint(result.message ?? "No running Unity for this project");
|
|
1023
|
+
setTimeout(() => {
|
|
1024
|
+
setHint(defaultHintMessage);
|
|
1025
|
+
}, 3e3);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
setHint(`Stopped Unity: ${projectView.project.title}`);
|
|
1029
|
+
setTimeout(() => {
|
|
1030
|
+
setHint(defaultHintMessage);
|
|
1031
|
+
}, 3e3);
|
|
1032
|
+
setLaunchedProjects((previous) => {
|
|
1033
|
+
if (!previous.has(projectView.project.id)) {
|
|
1034
|
+
return previous;
|
|
1035
|
+
}
|
|
1036
|
+
const next = new Set(previous);
|
|
1037
|
+
next.delete(projectView.project.id);
|
|
1038
|
+
return next;
|
|
1039
|
+
});
|
|
1040
|
+
setReleasedProjects((previous) => {
|
|
1041
|
+
const next = new Set(previous);
|
|
1042
|
+
next.add(projectView.project.id);
|
|
1043
|
+
return next;
|
|
1044
|
+
});
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1047
|
+
setHint(`Failed to stop: ${message}`);
|
|
1048
|
+
setTimeout(() => {
|
|
1049
|
+
setHint(defaultHintMessage);
|
|
1050
|
+
}, 3e3);
|
|
1051
|
+
}
|
|
1052
|
+
}, [index, onTerminate, sortedProjects]);
|
|
1053
|
+
useEffect(() => {
|
|
1054
|
+
setReleasedProjects(/* @__PURE__ */ new Set());
|
|
1055
|
+
setLaunchedProjects(/* @__PURE__ */ new Set());
|
|
1056
|
+
}, [projects]);
|
|
770
1057
|
useInput((input, key) => {
|
|
771
1058
|
if (input === "j" || key.downArrow) {
|
|
772
1059
|
move(1);
|
|
@@ -774,8 +1061,13 @@ var App = ({
|
|
|
774
1061
|
if (input === "k" || key.upArrow) {
|
|
775
1062
|
move(-1);
|
|
776
1063
|
}
|
|
1064
|
+
if (input === "q") {
|
|
1065
|
+
void terminateSelected();
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
777
1068
|
if (input === "o") {
|
|
778
1069
|
void launchSelected();
|
|
1070
|
+
return;
|
|
779
1071
|
}
|
|
780
1072
|
if (input === "c") {
|
|
781
1073
|
copyProjectPath();
|
|
@@ -833,6 +1125,7 @@ var App = ({
|
|
|
833
1125
|
const updatedText = formatUpdatedText(project.lastModified);
|
|
834
1126
|
const pathLine = shortenHomePath(project.path);
|
|
835
1127
|
const branchLine = formatBranch(repository?.branch);
|
|
1128
|
+
const activeLock = isLocked && !releasedProjects.has(project.id) || launchedProjects.has(project.id);
|
|
836
1129
|
const baseScrollbarIndex = offset * linesPerProject;
|
|
837
1130
|
const titleScrollbar = scrollbarChars[baseScrollbarIndex] ?? " ";
|
|
838
1131
|
const branchScrollbar = showBranch ? scrollbarChars[baseScrollbarIndex + 1] ?? " " : " ";
|
|
@@ -851,7 +1144,7 @@ var App = ({
|
|
|
851
1144
|
versionLabel
|
|
852
1145
|
] }),
|
|
853
1146
|
updatedText ? /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: ` ${updatedText}` }) : null,
|
|
854
|
-
|
|
1147
|
+
activeLock ? /* @__PURE__ */ jsx(Text, { color: LOCK_COLOR, children: ` ${LOCK_LABEL}` }) : null
|
|
855
1148
|
] }),
|
|
856
1149
|
showBranch ? /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : BRANCH_COLOR, children: [
|
|
857
1150
|
" ",
|
|
@@ -871,9 +1164,19 @@ var App = ({
|
|
|
871
1164
|
] })
|
|
872
1165
|
] }, project.id);
|
|
873
1166
|
});
|
|
874
|
-
}, [
|
|
1167
|
+
}, [
|
|
1168
|
+
index,
|
|
1169
|
+
launchedProjects,
|
|
1170
|
+
releasedProjects,
|
|
1171
|
+
scrollbarChars,
|
|
1172
|
+
showBranch,
|
|
1173
|
+
showPath,
|
|
1174
|
+
startIndex,
|
|
1175
|
+
useGitRootName,
|
|
1176
|
+
visibleProjects
|
|
1177
|
+
]);
|
|
875
1178
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
876
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", children: rows.length === 0 ? /* @__PURE__ */ jsx(Text, { children: "Unity Hub
|
|
1179
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", children: rows.length === 0 ? /* @__PURE__ */ jsx(Text, { children: "No Unity Hub projects were found." }) : rows }),
|
|
877
1180
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { children: hint }) })
|
|
878
1181
|
] });
|
|
879
1182
|
};
|
|
@@ -884,6 +1187,7 @@ var bootstrap = async () => {
|
|
|
884
1187
|
const unityHubReader = new UnityHubProjectsReader();
|
|
885
1188
|
const gitRepositoryInfoReader = new GitRepositoryInfoReader();
|
|
886
1189
|
const lockStatusReader = new UnityLockStatusReader();
|
|
1190
|
+
const unityProcessReader = new MacUnityProcessReader();
|
|
887
1191
|
const listProjectsUseCase = new ListProjectsUseCase(
|
|
888
1192
|
unityHubReader,
|
|
889
1193
|
gitRepositoryInfoReader,
|
|
@@ -892,7 +1196,9 @@ var bootstrap = async () => {
|
|
|
892
1196
|
);
|
|
893
1197
|
const editorPathResolver = new MacEditorPathResolver();
|
|
894
1198
|
const processLauncher = new NodeProcessLauncher();
|
|
895
|
-
const lockChecker = new UnityLockChecker();
|
|
1199
|
+
const lockChecker = new UnityLockChecker(unityProcessReader);
|
|
1200
|
+
const unityProcessTerminator = new MacUnityProcessTerminator();
|
|
1201
|
+
const unityTempDirectoryCleaner = new UnityTempDirectoryCleaner();
|
|
896
1202
|
const launchProjectUseCase = new LaunchProjectUseCase(
|
|
897
1203
|
editorPathResolver,
|
|
898
1204
|
processLauncher,
|
|
@@ -900,6 +1206,11 @@ var bootstrap = async () => {
|
|
|
900
1206
|
unityHubReader,
|
|
901
1207
|
lockChecker
|
|
902
1208
|
);
|
|
1209
|
+
const terminateProjectUseCase = new TerminateProjectUseCase(
|
|
1210
|
+
unityProcessReader,
|
|
1211
|
+
unityProcessTerminator,
|
|
1212
|
+
unityTempDirectoryCleaner
|
|
1213
|
+
);
|
|
903
1214
|
const useGitRootName = !process2.argv.includes("--no-git-root-name");
|
|
904
1215
|
const showBranch = !process2.argv.includes("--hide-branch");
|
|
905
1216
|
const showPath = !process2.argv.includes("--hide-path");
|
|
@@ -911,6 +1222,7 @@ var bootstrap = async () => {
|
|
|
911
1222
|
{
|
|
912
1223
|
projects,
|
|
913
1224
|
onLaunch: (project) => launchProjectUseCase.execute(project),
|
|
1225
|
+
onTerminate: (project) => terminateProjectUseCase.execute(project),
|
|
914
1226
|
useGitRootName,
|
|
915
1227
|
showBranch,
|
|
916
1228
|
showPath
|