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.
Files changed (2) hide show
  1. package/dist/index.js +324 -26
  2. 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.allSettled(
17
- projects.map((project) => this.gitRepositoryInfoReader.readRepositoryInfo(project.path))
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
- if (repositoryResult.status === "fulfilled") {
22
- return { project, repository: repositoryResult.value ?? void 0 };
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(`\u5BFE\u5FDC\u3059\u308BUnity Editor\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF08${version.value}\uFF09`);
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((resolve2, reject) => {
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
- resolve2();
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\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF08${HUB_PROJECTS_PATH}\uFF09`
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\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\uFF08\u6A29\u9650/\u5F62\u5F0F\u30A8\u30E9\u30FC\uFF09");
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(`\u672A\u5BFE\u5FDC\u306Eschema_version\u3067\u3059\uFF08${json.schema_version}\uFF09`);
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\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF08${HUB_PROJECTS_PATH}\uFF09`
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\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\uFF08\u6A29\u9650/\u5F62\u5F0F\u30A8\u30E9\u30FC\uFF09");
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((resolve2) => {
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
- resolve2(result);
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((resolve2) => {
448
+ const confirmed = await new Promise((resolve3) => {
410
449
  prompt.rl.question(RAW_PROMPT_MESSAGE, (answer) => {
411
- resolve2(answer.trim() === "y");
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 Copy cd path with c \xB7 Exit with Ctrl+C";
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
- }, [index, scrollbarChars, showBranch, showPath, startIndex, useGitRootName, visibleProjects]);
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\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F" }) : rows }),
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unity-hub-cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "A CLI tool that reads Unity Hub's projects and launches Unity Editor with an interactive TUI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {