unity-hub-cli 0.11.0 → 0.13.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 (3) hide show
  1. package/README.md +13 -2
  2. package/dist/index.js +396 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,9 +8,12 @@ A CLI tool that displays the same content as Unity Hub in an Ink-based TUI, allo
8
8
 
9
9
  ## Requirements
10
10
 
11
- - macOS
11
+ - macOS or Windows 10/11
12
12
  - Node.js 20+
13
- - Unity Hub (with `~/Library/Application Support/UnityHub/projects-v1.json` present)
13
+ - Unity Hub
14
+ - macOS: `~/Library/Application Support/UnityHub/projects-v1.json`
15
+ - Windows: `%APPDATA%\UnityHub\projects-v1.json`
16
+ - Windows Editor path (default): `C:\\Program Files\\Unity\\Hub\\Editor\\<version>\\Editor\\Unity.exe`
14
17
 
15
18
  ## Usage
16
19
 
@@ -37,6 +40,14 @@ npx unity-hub-cli
37
40
  node dist/index.js
38
41
  ```
39
42
 
43
+ On Windows, it works from PowerShell and CMD. Git Bash is supported when running inside a ConPTY-based terminal (Windows Terminal or VS Code/Cursor integrated terminal). On standalone Git Bash (MinTTY), raw mode is not supported; use PowerShell/CMD/Windows Terminal. If you must use MinTTY Git Bash, run one of the following:
44
+
45
+ - `winpty cmd.exe /c npx unity-hub-cli`
46
+ - `winpty powershell.exe -NoProfile -Command npx unity-hub-cli`
47
+ - If already built: `npm run build && winpty node dist/index.js`
48
+
49
+ See `https://github.com/vadimdemedes/ink/#israwmodesupported`.
50
+
40
51
  By default, the project list uses the Git repository root folder name when available.
41
52
 
42
53
  ### CLI Options
package/dist/index.js CHANGED
@@ -124,9 +124,43 @@ var MacEditorPathResolver = class {
124
124
  }
125
125
  };
126
126
 
127
+ // src/infrastructure/editor.win.ts
128
+ import { constants as constants2 } from "fs";
129
+ import { access as access2 } from "fs/promises";
130
+ import { join as join2 } from "path";
131
+ var buildCandidateBases = () => {
132
+ const candidates = [];
133
+ const programFiles = process.env.PROGRAMFILES ?? "C:\\Program Files";
134
+ const programW6432 = process.env.ProgramW6432 ?? process.env.PROGRAMFILES;
135
+ const localAppData = process.env.LOCALAPPDATA;
136
+ candidates.push(join2(programFiles, "Unity", "Hub", "Editor"));
137
+ if (programW6432) {
138
+ candidates.push(join2(programW6432, "Unity", "Hub", "Editor"));
139
+ }
140
+ if (localAppData) {
141
+ candidates.push(join2(localAppData, "Unity", "Hub", "Editor"));
142
+ }
143
+ return Array.from(new Set(candidates));
144
+ };
145
+ var WinEditorPathResolver = class {
146
+ async resolve(version) {
147
+ const tried = [];
148
+ for (const base of buildCandidateBases()) {
149
+ const candidate = join2(base, version.value, "Editor", "Unity.exe");
150
+ try {
151
+ await access2(candidate, constants2.F_OK);
152
+ return candidate;
153
+ } catch {
154
+ tried.push(candidate);
155
+ }
156
+ }
157
+ throw new Error(`Unity Editor not found for version ${version.value}. Tried: ${tried.join(" , ")}`);
158
+ }
159
+ };
160
+
127
161
  // src/infrastructure/git.ts
128
162
  import { readFile, stat } from "fs/promises";
129
- import { dirname, join as join2, resolve } from "path";
163
+ import { dirname, join as join3, resolve } from "path";
130
164
  var HEAD_FILE = "HEAD";
131
165
  var GIT_DIR = ".git";
132
166
  var MAX_ASCENT = 50;
@@ -149,7 +183,7 @@ var isFile = async (path) => {
149
183
  var findGitDir = async (start) => {
150
184
  let current = resolve(start);
151
185
  for (let depth = 0; depth < MAX_ASCENT; depth += 1) {
152
- const candidate = join2(current, GIT_DIR);
186
+ const candidate = join3(current, GIT_DIR);
153
187
  if (await isDirectory(candidate)) {
154
188
  return candidate;
155
189
  }
@@ -193,7 +227,7 @@ var GitRepositoryInfoReader = class {
193
227
  return void 0;
194
228
  }
195
229
  try {
196
- const headPath = join2(gitDir, HEAD_FILE);
230
+ const headPath = join3(gitDir, HEAD_FILE);
197
231
  const content = await readFile(headPath, "utf8");
198
232
  const branch = parseHead(content);
199
233
  const root = dirname(gitDir);
@@ -209,7 +243,7 @@ import { spawn } from "child_process";
209
243
  var NodeProcessLauncher = class {
210
244
  async launch(command, args, options) {
211
245
  const detached = options?.detached ?? false;
212
- await new Promise((resolve3, reject) => {
246
+ await new Promise((resolve4, reject) => {
213
247
  const child = spawn(command, args, {
214
248
  detached,
215
249
  stdio: "ignore"
@@ -221,7 +255,7 @@ var NodeProcessLauncher = class {
221
255
  const handleSpawn = () => {
222
256
  child.off("error", handleError);
223
257
  child.unref();
224
- resolve3();
258
+ resolve4();
225
259
  };
226
260
  child.once("error", handleError);
227
261
  child.once("spawn", handleSpawn);
@@ -275,7 +309,7 @@ var sortByFavoriteThenLastModified = (projects) => {
275
309
  return timeB - timeA;
276
310
  });
277
311
  };
278
- var UnityHubProjectsReader = class {
312
+ var MacUnityHubProjectsReader = class {
279
313
  async listProjects() {
280
314
  let content;
281
315
  try {
@@ -359,11 +393,142 @@ var UnityHubProjectsReader = class {
359
393
  }
360
394
  };
361
395
 
396
+ // src/infrastructure/unityhub.win.ts
397
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
398
+ import { basename as basename2, join as join4 } from "path";
399
+ var HUB_DIR = join4(process.env.APPDATA ?? "", "UnityHub");
400
+ var HUB_PROJECTS_PATH2 = join4(HUB_DIR, "projects-v1.json");
401
+ var schemaVersion2 = "v1";
402
+ var toUnityProject2 = (entry) => {
403
+ const safePath = entry.path;
404
+ if (!safePath) {
405
+ throw new Error("Unity Hub entry is missing project path");
406
+ }
407
+ const version = entry.version;
408
+ if (!version) {
409
+ throw new Error(`Unity Hub entry ${safePath} is missing version`);
410
+ }
411
+ const lastModified = typeof entry.lastModified === "number" ? new Date(entry.lastModified) : void 0;
412
+ return {
413
+ id: safePath,
414
+ title: entry.title?.trim() || basename2(safePath),
415
+ path: safePath,
416
+ version: { value: version },
417
+ lastModified,
418
+ favorite: entry.isFavorite === true
419
+ };
420
+ };
421
+ var normalizeValue2 = (value) => value.toLocaleLowerCase();
422
+ var sortByFavoriteThenLastModified2 = (projects) => {
423
+ return [...projects].sort((a, b) => {
424
+ const favoriteRankA = a.favorite ? 0 : 1;
425
+ const favoriteRankB = b.favorite ? 0 : 1;
426
+ if (favoriteRankA !== favoriteRankB) {
427
+ return favoriteRankA - favoriteRankB;
428
+ }
429
+ const fallbackTime = 0;
430
+ const timeA = a.lastModified?.getTime() ?? fallbackTime;
431
+ const timeB = b.lastModified?.getTime() ?? fallbackTime;
432
+ if (timeA === timeB) {
433
+ const titleA = normalizeValue2(a.title);
434
+ const titleB = normalizeValue2(b.title);
435
+ if (titleA === titleB) {
436
+ return normalizeValue2(a.path).localeCompare(normalizeValue2(b.path));
437
+ }
438
+ return titleA.localeCompare(titleB);
439
+ }
440
+ return timeB - timeA;
441
+ });
442
+ };
443
+ var WinUnityHubProjectsReader = class {
444
+ async listProjects() {
445
+ let content;
446
+ try {
447
+ content = await readFile3(HUB_PROJECTS_PATH2, "utf8");
448
+ } catch {
449
+ throw new Error(
450
+ `Unity Hub project list not found (${HUB_PROJECTS_PATH2}).`
451
+ );
452
+ }
453
+ let json;
454
+ try {
455
+ json = JSON.parse(content);
456
+ } catch {
457
+ throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
458
+ }
459
+ if (json.schema_version && json.schema_version !== schemaVersion2) {
460
+ throw new Error(`Unsupported schema_version (${json.schema_version}).`);
461
+ }
462
+ const entries = Object.values(json.data ?? {});
463
+ if (entries.length === 0) {
464
+ return [];
465
+ }
466
+ const projects = entries.map(toUnityProject2);
467
+ return sortByFavoriteThenLastModified2(projects);
468
+ }
469
+ async updateLastModified(projectPath, date) {
470
+ let content;
471
+ try {
472
+ content = await readFile3(HUB_PROJECTS_PATH2, "utf8");
473
+ } catch {
474
+ throw new Error(
475
+ `Unity Hub project list not found (${HUB_PROJECTS_PATH2}).`
476
+ );
477
+ }
478
+ let json;
479
+ try {
480
+ json = JSON.parse(content);
481
+ } catch {
482
+ throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
483
+ }
484
+ if (!json.data) {
485
+ return;
486
+ }
487
+ const projectKey = Object.keys(json.data).find((key) => json.data?.[key]?.path === projectPath);
488
+ if (!projectKey) {
489
+ return;
490
+ }
491
+ const original = json.data[projectKey];
492
+ if (!original) {
493
+ return;
494
+ }
495
+ json.data[projectKey] = {
496
+ ...original,
497
+ lastModified: date.getTime()
498
+ };
499
+ await writeFile2(HUB_PROJECTS_PATH2, JSON.stringify(json, void 0, 2), "utf8");
500
+ }
501
+ async readCliArgs(projectPath) {
502
+ const infoPath = join4(HUB_DIR, "projectsInfo.json");
503
+ let content;
504
+ try {
505
+ content = await readFile3(infoPath, "utf8");
506
+ } catch {
507
+ return [];
508
+ }
509
+ let json;
510
+ try {
511
+ json = JSON.parse(content);
512
+ } catch {
513
+ return [];
514
+ }
515
+ const entry = json[projectPath];
516
+ if (!entry?.cliArgs) {
517
+ return [];
518
+ }
519
+ const tokens = entry.cliArgs.match(/(?:"[^"]*"|'[^']*'|[^\s"']+)/g);
520
+ if (!tokens) {
521
+ return [];
522
+ }
523
+ return tokens.map((token) => token.replace(/^['"]|['"]$/g, ""));
524
+ }
525
+ };
526
+
362
527
  // src/infrastructure/unityLock.ts
363
528
  import { execFile } from "child_process";
364
- import { constants as constants2 } from "fs";
365
- import { access as access2, rm } from "fs/promises";
366
- import { join as join3 } from "path";
529
+ import { constants as constants3 } from "fs";
530
+ import { access as access3, rm } from "fs/promises";
531
+ import { join as join5 } from "path";
367
532
  import { promisify } from "util";
368
533
  var execFileAsync = promisify(execFile);
369
534
  var buildBringToFrontScript = (pid) => {
@@ -371,7 +536,7 @@ var buildBringToFrontScript = (pid) => {
371
536
  };
372
537
  var pathExists = async (target) => {
373
538
  try {
374
- await access2(target, constants2.F_OK);
539
+ await access3(target, constants3.F_OK);
375
540
  return true;
376
541
  } catch {
377
542
  return false;
@@ -391,7 +556,7 @@ var UnityLockChecker = class {
391
556
  await this.bringUnityToFront(activeProcess.pid);
392
557
  return "skip";
393
558
  }
394
- const lockfilePath = join3(projectPath, "Temp", "UnityLockfile");
559
+ const lockfilePath = join5(projectPath, "Temp", "UnityLockfile");
395
560
  const hasLockfile = await pathExists(lockfilePath);
396
561
  if (!hasLockfile) {
397
562
  return "allow";
@@ -429,7 +594,7 @@ var UnityLockChecker = class {
429
594
  };
430
595
  var UnityLockStatusReader = class {
431
596
  async isLocked(projectPath) {
432
- const lockfilePath = join3(projectPath, "Temp", "UnityLockfile");
597
+ const lockfilePath = join5(projectPath, "Temp", "UnityLockfile");
433
598
  return await pathExists(lockfilePath);
434
599
  }
435
600
  };
@@ -602,13 +767,187 @@ var MacUnityProcessTerminator = class {
602
767
  }
603
768
  };
604
769
 
770
+ // src/infrastructure/unityProcess.win.ts
771
+ import { execFile as execFile3 } from "child_process";
772
+ import { resolve as resolve3 } from "path";
773
+ import { promisify as promisify3 } from "util";
774
+ var execFileAsync3 = promisify3(execFile3);
775
+ var PROJECT_PATH_PATTERN2 = /-(?:projectPath|projectpath)(?:=|\s+)("[^"]+"|'[^']+'|[^\s"']+)/i;
776
+ var TERMINATE_TIMEOUT_MILLIS2 = 5e3;
777
+ var TERMINATE_POLL_INTERVAL_MILLIS2 = 200;
778
+ var delay2 = async (duration) => {
779
+ await new Promise((resolveDelay) => {
780
+ setTimeout(() => {
781
+ resolveDelay();
782
+ }, duration);
783
+ });
784
+ };
785
+ var normalizePath2 = (target) => {
786
+ const resolved = resolve3(target);
787
+ if (resolved.endsWith("/") || resolved.endsWith("\\")) {
788
+ return resolved.slice(0, -1);
789
+ }
790
+ return resolved;
791
+ };
792
+ var arePathsEqual2 = (left, right) => {
793
+ const normalizedLeft = normalizePath2(left);
794
+ const normalizedRight = normalizePath2(right);
795
+ return normalizedLeft.localeCompare(normalizedRight, void 0, { sensitivity: "base" }) === 0;
796
+ };
797
+ var extractProjectPath2 = (command) => {
798
+ const match = command.match(PROJECT_PATH_PATTERN2);
799
+ if (!match) {
800
+ return void 0;
801
+ }
802
+ const raw = match[1];
803
+ if (!raw) {
804
+ return void 0;
805
+ }
806
+ const trimmed = raw.trim();
807
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
808
+ return trimmed.slice(1, -1);
809
+ }
810
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
811
+ return trimmed.slice(1, -1);
812
+ }
813
+ return trimmed;
814
+ };
815
+ var isProcessMissingError2 = (error) => {
816
+ if (typeof error !== "object" || error === null) {
817
+ return false;
818
+ }
819
+ const nodeError = error;
820
+ return nodeError.code === "ESRCH";
821
+ };
822
+ var ensureProcessAlive2 = (pid) => {
823
+ try {
824
+ process.kill(pid, 0);
825
+ return true;
826
+ } catch (error) {
827
+ if (isProcessMissingError2(error)) {
828
+ return false;
829
+ }
830
+ return false;
831
+ }
832
+ };
833
+ var parsePowershellJson = (jsonText) => {
834
+ try {
835
+ const parsed = JSON.parse(jsonText);
836
+ if (!parsed) {
837
+ return [];
838
+ }
839
+ if (Array.isArray(parsed)) {
840
+ return parsed.filter((r) => r && typeof r === "object" && typeof r.ProcessId === "number");
841
+ }
842
+ const single = parsed;
843
+ if (typeof single.ProcessId === "number") {
844
+ return [single];
845
+ }
846
+ return [];
847
+ } catch {
848
+ return [];
849
+ }
850
+ };
851
+ var WinUnityProcessReader = class {
852
+ async findByProjectPath(projectPath) {
853
+ const normalizedTarget = normalizePath2(projectPath);
854
+ const processes = await this.listUnityProcesses();
855
+ return processes.find((candidate) => arePathsEqual2(candidate.projectPath, normalizedTarget));
856
+ }
857
+ async listUnityProcesses() {
858
+ const psCommand = [
859
+ "$ErrorActionPreference = 'SilentlyContinue';",
860
+ `$procs = Get-CimInstance Win32_Process -Filter "Name='Unity.exe'";`,
861
+ "$procs | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress"
862
+ ].join(" ");
863
+ let stdout;
864
+ try {
865
+ const result = await execFileAsync3(
866
+ "powershell.exe",
867
+ [
868
+ "-NoProfile",
869
+ "-NonInteractive",
870
+ "-ExecutionPolicy",
871
+ "Bypass",
872
+ "-Command",
873
+ psCommand
874
+ ],
875
+ { encoding: "utf8" }
876
+ );
877
+ stdout = (result.stdout ?? "").trim();
878
+ } catch (error) {
879
+ throw new Error(`Failed to retrieve Unity process list: ${error instanceof Error ? error.message : String(error)}`);
880
+ }
881
+ const rows = parsePowershellJson(stdout);
882
+ return rows.map((row) => {
883
+ const pidValue = row.ProcessId;
884
+ if (!Number.isFinite(pidValue)) {
885
+ return void 0;
886
+ }
887
+ const commandLine = row.CommandLine ?? "";
888
+ const projectArgument = extractProjectPath2(commandLine);
889
+ if (!projectArgument) {
890
+ return void 0;
891
+ }
892
+ return {
893
+ pid: pidValue,
894
+ projectPath: normalizePath2(projectArgument)
895
+ };
896
+ }).filter((p) => Boolean(p));
897
+ }
898
+ };
899
+ var WinUnityProcessTerminator = class {
900
+ async terminate(unityProcess) {
901
+ try {
902
+ await execFileAsync3("powershell.exe", [
903
+ "-NoProfile",
904
+ "-NonInteractive",
905
+ "-ExecutionPolicy",
906
+ "Bypass",
907
+ "-Command",
908
+ `Stop-Process -Id ${unityProcess.pid}`
909
+ ]);
910
+ } catch (error) {
911
+ if (!ensureProcessAlive2(unityProcess.pid)) {
912
+ return { terminated: true, stage: "sigterm" };
913
+ }
914
+ }
915
+ const deadline = Date.now() + TERMINATE_TIMEOUT_MILLIS2;
916
+ while (Date.now() < deadline) {
917
+ await delay2(TERMINATE_POLL_INTERVAL_MILLIS2);
918
+ const alive = ensureProcessAlive2(unityProcess.pid);
919
+ if (!alive) {
920
+ return { terminated: true, stage: "sigterm" };
921
+ }
922
+ }
923
+ try {
924
+ await execFileAsync3("powershell.exe", [
925
+ "-NoProfile",
926
+ "-NonInteractive",
927
+ "-ExecutionPolicy",
928
+ "Bypass",
929
+ "-Command",
930
+ `Stop-Process -Id ${unityProcess.pid} -Force`
931
+ ]);
932
+ } catch (error) {
933
+ if (!ensureProcessAlive2(unityProcess.pid)) {
934
+ return { terminated: true, stage: "sigkill" };
935
+ }
936
+ return { terminated: false };
937
+ }
938
+ await delay2(TERMINATE_POLL_INTERVAL_MILLIS2);
939
+ const aliveAfterKill = ensureProcessAlive2(unityProcess.pid);
940
+ return aliveAfterKill ? { terminated: false } : { terminated: true, stage: "sigkill" };
941
+ }
942
+ };
943
+
605
944
  // src/infrastructure/unityTemp.ts
606
945
  import { rm as rm2 } from "fs/promises";
607
- import { join as join4 } from "path";
946
+ import { join as join6 } from "path";
608
947
  var TEMP_DIRECTORY_NAME = "Temp";
609
948
  var UnityTempDirectoryCleaner = class {
610
949
  async clean(projectPath) {
611
- const tempDirectoryPath = join4(projectPath, TEMP_DIRECTORY_NAME);
950
+ const tempDirectoryPath = join6(projectPath, TEMP_DIRECTORY_NAME);
612
951
  try {
613
952
  await rm2(tempDirectoryPath, {
614
953
  recursive: true,
@@ -685,6 +1024,13 @@ var shortenHomePath = (targetPath) => {
685
1024
  };
686
1025
  var buildCdCommand = (targetPath) => {
687
1026
  if (process.platform === "win32") {
1027
+ const isGitBash = Boolean(process.env.MSYSTEM) || /bash/i.test(process.env.SHELL ?? "");
1028
+ if (isGitBash) {
1029
+ const windowsPath = targetPath;
1030
+ const msysPath = windowsPath.replace(/^([A-Za-z]):[\\/]/, (_, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, "/");
1031
+ const escapedForPosix2 = msysPath.replace(/'/g, "'\\''");
1032
+ return `cd '${escapedForPosix2}'`;
1033
+ }
688
1034
  const escapedForWindows = targetPath.replace(/"/g, '""');
689
1035
  return `cd "${escapedForWindows}"`;
690
1036
  }
@@ -1015,7 +1361,7 @@ var VisibilityPanel = ({ visibility, focusedIndex, width }) => {
1015
1361
  import { useEffect, useState } from "react";
1016
1362
 
1017
1363
  // src/infrastructure/config.ts
1018
- import { mkdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
1364
+ import { mkdir, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
1019
1365
  var defaultSortPreferences = {
1020
1366
  favoritesFirst: true,
1021
1367
  primary: "updated",
@@ -1030,6 +1376,10 @@ var defaultAppConfig = {
1030
1376
  visibility: defaultVisibilityPreferences
1031
1377
  };
1032
1378
  var getConfigDir = () => {
1379
+ if (process.platform === "win32") {
1380
+ const appdata = process.env.APPDATA ?? "";
1381
+ return appdata ? `${appdata}\\UnityHubCli` : "UnityHubCli";
1382
+ }
1033
1383
  const home = process.env.HOME ?? "";
1034
1384
  return `${home}/Library/Application Support/UnityHubCli`;
1035
1385
  };
@@ -1066,7 +1416,7 @@ var sanitizeAppConfig = (input) => {
1066
1416
  };
1067
1417
  var readAppConfig = async () => {
1068
1418
  try {
1069
- const content = await readFile3(getConfigPath(), "utf8");
1419
+ const content = await readFile4(getConfigPath(), "utf8");
1070
1420
  const json = JSON.parse(content);
1071
1421
  return sanitizeAppConfig(json);
1072
1422
  } catch {
@@ -1079,7 +1429,7 @@ var writeAppConfig = async (config) => {
1079
1429
  } catch {
1080
1430
  }
1081
1431
  const json = JSON.stringify(sanitizeAppConfig(config), void 0, 2);
1082
- await writeFile2(getConfigPath(), json, "utf8");
1432
+ await writeFile3(getConfigPath(), json, "utf8");
1083
1433
  };
1084
1434
  var readSortPreferences = async () => {
1085
1435
  const config = await readAppConfig();
@@ -1224,6 +1574,9 @@ var App = ({
1224
1574
  const [isRefreshing, setIsRefreshing] = useState4(false);
1225
1575
  const [sortMenuIndex, setSortMenuIndex] = useState4(0);
1226
1576
  const { sortPreferences, setSortPreferences } = useSortPreferences();
1577
+ const clearScreen = useCallback(() => {
1578
+ stdout?.write("\x1B[2J\x1B[3J\x1B[H");
1579
+ }, [stdout]);
1227
1580
  const sortedProjects = useMemo2(() => {
1228
1581
  const fallbackTime = 0;
1229
1582
  const getNameKey = (view) => {
@@ -1533,6 +1886,7 @@ var App = ({
1533
1886
  useInput((input, key) => {
1534
1887
  if (isSortMenuOpen) {
1535
1888
  if (key.escape || input === "\x1B") {
1889
+ clearScreen();
1536
1890
  setIsSortMenuOpen(false);
1537
1891
  return;
1538
1892
  }
@@ -1570,6 +1924,7 @@ var App = ({
1570
1924
  }
1571
1925
  if (isVisibilityMenuOpen) {
1572
1926
  if (key.escape || input === "\x1B") {
1927
+ clearScreen();
1573
1928
  setIsVisibilityMenuOpen(false);
1574
1929
  return;
1575
1930
  }
@@ -1602,12 +1957,14 @@ var App = ({
1602
1957
  return;
1603
1958
  }
1604
1959
  if (input === "S" || input === "s") {
1960
+ clearScreen();
1605
1961
  setIsVisibilityMenuOpen(false);
1606
1962
  setIsSortMenuOpen(true);
1607
1963
  setSortMenuIndex(0);
1608
1964
  return;
1609
1965
  }
1610
1966
  if (input === "v" || input === "V") {
1967
+ clearScreen();
1611
1968
  setIsSortMenuOpen(false);
1612
1969
  setIsVisibilityMenuOpen(true);
1613
1970
  setSortMenuIndex(0);
@@ -1711,10 +2068,11 @@ var App = ({
1711
2068
  // src/index.tsx
1712
2069
  import { jsx as jsx7 } from "react/jsx-runtime";
1713
2070
  var bootstrap = async () => {
1714
- const unityHubReader = new UnityHubProjectsReader();
2071
+ const isWindows = process2.platform === "win32";
2072
+ const unityHubReader = isWindows ? new WinUnityHubProjectsReader() : new MacUnityHubProjectsReader();
1715
2073
  const gitRepositoryInfoReader = new GitRepositoryInfoReader();
1716
2074
  const lockStatusReader = new UnityLockStatusReader();
1717
- const unityProcessReader = new MacUnityProcessReader();
2075
+ const unityProcessReader = isWindows ? new WinUnityProcessReader() : new MacUnityProcessReader();
1718
2076
  const unityTempDirectoryCleaner = new UnityTempDirectoryCleaner();
1719
2077
  const listProjectsUseCase = new ListProjectsUseCase(
1720
2078
  unityHubReader,
@@ -1723,10 +2081,10 @@ var bootstrap = async () => {
1723
2081
  lockStatusReader,
1724
2082
  unityProcessReader
1725
2083
  );
1726
- const editorPathResolver = new MacEditorPathResolver();
2084
+ const editorPathResolver = isWindows ? new WinEditorPathResolver() : new MacEditorPathResolver();
1727
2085
  const processLauncher = new NodeProcessLauncher();
1728
2086
  const lockChecker = new UnityLockChecker(unityProcessReader, unityTempDirectoryCleaner);
1729
- const unityProcessTerminator = new MacUnityProcessTerminator();
2087
+ const unityProcessTerminator = isWindows ? new WinUnityProcessTerminator() : new MacUnityProcessTerminator();
1730
2088
  const launchProjectUseCase = new LaunchProjectUseCase(
1731
2089
  editorPathResolver,
1732
2090
  processLauncher,
@@ -1741,6 +2099,23 @@ var bootstrap = async () => {
1741
2099
  );
1742
2100
  const useGitRootName = !process2.argv.includes("--no-git-root-name");
1743
2101
  try {
2102
+ const rawModeSupported = Boolean(
2103
+ process2.stdin.isTTY && typeof process2.stdin.setRawMode === "function"
2104
+ );
2105
+ if (!rawModeSupported) {
2106
+ const message = [
2107
+ "\u3053\u306E\u7AEF\u672B\u3067\u306F\u5BFE\u8A71\u5165\u529B\uFF08Raw mode\uFF09\u304C\u4F7F\u3048\u307E\u305B\u3093\u3002",
2108
+ "PowerShell / cmd.exe \u3067\u5B9F\u884C\u3059\u308B\u304B\u3001ConPTY \u30D9\u30FC\u30B9\u306E\u30BF\u30FC\u30DF\u30CA\u30EB\uFF08Windows Terminal, VS Code/Cursor \u306E\u7D71\u5408\u30BF\u30FC\u30DF\u30CA\u30EB\uFF09\u3067 Git Bash \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
2109
+ "MinTTY \u306E Git Bash \u3067\u306F\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044:",
2110
+ " - winpty cmd.exe /c npx unity-hub-cli",
2111
+ " - winpty powershell.exe -NoProfile -Command npx unity-hub-cli",
2112
+ "\uFF08\u30D3\u30EB\u30C9\u6E08\u307F\u306E\u5834\u5408\uFF09npm run build && winpty node dist/index.js",
2113
+ "\u8A73\u3057\u304F: https://github.com/vadimdemedes/ink/#israwmodesupported"
2114
+ ].join("\n");
2115
+ console.error(message);
2116
+ process2.exitCode = 1;
2117
+ return;
2118
+ }
1744
2119
  const projects = await listProjectsUseCase.execute();
1745
2120
  const { waitUntilExit } = render(
1746
2121
  /* @__PURE__ */ jsx7(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unity-hub-cli",
3
- "version": "0.11.0",
3
+ "version": "0.13.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": {