unity-hub-cli 0.18.0 → 0.20.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 +2 -1
  2. package/dist/index.js +343 -13
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -46,7 +46,7 @@ On standalone Git Bash (MinTTY), raw mode is not supported; use PowerShell/CMD/W
46
46
  | `i` | Launch external editor only |
47
47
  | `q` | Quit Unity for selected project |
48
48
  | `r` | Refresh project list |
49
- | `c` | Copy project path to clipboard |
49
+ | `c` | Copy project path to clipboard (when launched via the `unity-hub` shell function, it will `cd` into the project directory and exit) |
50
50
  | `s` | Open sort settings panel |
51
51
  | `v` | Open visibility settings panel |
52
52
  | `Ctrl + C` | Exit |
@@ -60,6 +60,7 @@ The display includes Git branch (if present), Unity version, project path, and l
60
60
  - `--no-git-root-name`: Display Unity project titles instead of Git repository root folder names.
61
61
  - `--shell-init`: Install shell function for automatic `cd` integration (with confirmation prompt).
62
62
  - `--shell-init --dry-run`: Preview the shell function without installing.
63
+ - `update`: Update the globally installed `unity-hub-cli` to the latest version (with a confirmation prompt). It runs: `npm install -g unity-hub-cli@latest --ignore-scripts --no-fund`.
63
64
 
64
65
  ## Shell Integration
65
66
 
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.tsx
4
- import { execSync } from "child_process";
4
+ import { execSync, spawnSync } from "child_process";
5
5
  import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
6
6
  import { homedir } from "os";
7
7
  import { dirname as dirname2, join as join9 } from "path";
8
8
  import process3 from "process";
9
9
  import { createInterface as createInterface2 } from "readline";
10
+ import { fileURLToPath } from "url";
10
11
  import chalk from "chalk";
11
12
  import { render } from "ink";
12
13
 
@@ -784,6 +785,38 @@ var MacUnityHubProjectsReader = class {
784
785
  };
785
786
  await writeFile(HUB_PROJECTS_PATH, JSON.stringify(json, void 0, 2), "utf8");
786
787
  }
788
+ async toggleFavorite(projectPath) {
789
+ let content;
790
+ try {
791
+ content = await readFile2(HUB_PROJECTS_PATH, "utf8");
792
+ } catch {
793
+ throw new Error(`Unity Hub project list not found (${HUB_PROJECTS_PATH}).`);
794
+ }
795
+ let json;
796
+ try {
797
+ json = JSON.parse(content);
798
+ } catch {
799
+ throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
800
+ }
801
+ if (!json.data) {
802
+ return false;
803
+ }
804
+ const projectKey = Object.keys(json.data).find((key) => json.data?.[key]?.path === projectPath);
805
+ if (!projectKey) {
806
+ return false;
807
+ }
808
+ const original = json.data[projectKey];
809
+ if (!original) {
810
+ return false;
811
+ }
812
+ const newFavorite = original.isFavorite !== true;
813
+ json.data[projectKey] = {
814
+ ...original,
815
+ isFavorite: newFavorite
816
+ };
817
+ await writeFile(HUB_PROJECTS_PATH, JSON.stringify(json, void 0, 2), "utf8");
818
+ return newFavorite;
819
+ }
787
820
  async readCliArgs(projectPath) {
788
821
  const infoPath = `${process.env.HOME ?? ""}/Library/Application Support/UnityHub/projectsInfo.json`;
789
822
  let content;
@@ -915,6 +948,38 @@ var WinUnityHubProjectsReader = class {
915
948
  };
916
949
  await writeFile2(HUB_PROJECTS_PATH2, JSON.stringify(json, void 0, 2), "utf8");
917
950
  }
951
+ async toggleFavorite(projectPath) {
952
+ let content;
953
+ try {
954
+ content = await readFile3(HUB_PROJECTS_PATH2, "utf8");
955
+ } catch {
956
+ throw new Error(`Unity Hub project list not found (${HUB_PROJECTS_PATH2}).`);
957
+ }
958
+ let json;
959
+ try {
960
+ json = JSON.parse(content);
961
+ } catch {
962
+ throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
963
+ }
964
+ if (!json.data) {
965
+ return false;
966
+ }
967
+ const projectKey = Object.keys(json.data).find((key) => json.data?.[key]?.path === projectPath);
968
+ if (!projectKey) {
969
+ return false;
970
+ }
971
+ const original = json.data[projectKey];
972
+ if (!original) {
973
+ return false;
974
+ }
975
+ const newFavorite = original.isFavorite !== true;
976
+ json.data[projectKey] = {
977
+ ...original,
978
+ isFavorite: newFavorite
979
+ };
980
+ await writeFile2(HUB_PROJECTS_PATH2, JSON.stringify(json, void 0, 2), "utf8");
981
+ return newFavorite;
982
+ }
918
983
  async readCliArgs(projectPath) {
919
984
  const infoPath = join6(HUB_DIR, "projectsInfo.json");
920
985
  let content;
@@ -1673,7 +1738,8 @@ var ProjectList = ({
1673
1738
  const rowIndex = startIndex + offset;
1674
1739
  const isSelected = rowIndex === selectedIndex;
1675
1740
  const selectionBar = isSelected ? "\u2503" : " ";
1676
- const projectName = formatProjectName(project.title, repository, useGitRootName);
1741
+ const baseName = formatProjectName(project.title, repository, useGitRootName);
1742
+ const projectName = project.favorite ? `\u2B51 ${baseName}` : baseName;
1677
1743
  const versionLabel = `(${project.version.value})`;
1678
1744
  const updatedText = formatUpdatedText(project.lastModified);
1679
1745
  const pathLine = shortenHomePath(project.path);
@@ -1993,11 +2059,11 @@ var extractRootFolder2 = (repository) => {
1993
2059
  return base || void 0;
1994
2060
  };
1995
2061
  var minimumVisibleProjectCount = 4;
1996
- var defaultHintMessage = `j/k Select \xB7 [o]pen [O]+Editor [i]de [q]uit [r]efresh [c]opy [s]ort [v]isibility \xB7 ^C Exit`;
1997
- var getCopyTargetPath = (view) => {
1998
- const root = view.repository?.root;
1999
- return root && root.length > 0 ? root : view.project.path;
2062
+ var buildDefaultHintMessage = (outputPathOnExit) => {
2063
+ const cLabel = outputPathOnExit ? "cd" : "copy";
2064
+ return `j/k Select \xB7 [o]pen [O]+Editor [i]de [q]uit [r]efresh [c]${cLabel} [f]av [s]ort [v]isibility \xB7 ^C Exit`;
2000
2065
  };
2066
+ var getCopyTargetPath = (view) => view.project.path;
2001
2067
  var App = ({
2002
2068
  projects,
2003
2069
  onLaunch,
@@ -2005,6 +2071,7 @@ var App = ({
2005
2071
  onLaunchEditorOnly,
2006
2072
  onTerminate,
2007
2073
  onRefresh,
2074
+ onToggleFavorite,
2008
2075
  useGitRootName = true,
2009
2076
  outputPathOnExit = false,
2010
2077
  onSetExitPath
@@ -2012,6 +2079,10 @@ var App = ({
2012
2079
  const { exit } = useApp();
2013
2080
  const { stdout } = useStdout2();
2014
2081
  const colors = useThemeColors();
2082
+ const defaultHintMessage = useMemo2(
2083
+ () => buildDefaultHintMessage(outputPathOnExit),
2084
+ [outputPathOnExit]
2085
+ );
2015
2086
  const [projectViews, setProjectViews] = useState4(projects);
2016
2087
  const [isSortMenuOpen, setIsSortMenuOpen] = useState4(false);
2017
2088
  const [isVisibilityMenuOpen, setIsVisibilityMenuOpen] = useState4(false);
@@ -2172,15 +2243,21 @@ var App = ({
2172
2243
  });
2173
2244
  }, [index, limit, sortedProjects.length]);
2174
2245
  const copyProjectPath = useCallback(() => {
2246
+ const actionLabel = outputPathOnExit ? "cd" : "copy";
2175
2247
  const projectView = sortedProjects[index];
2176
2248
  const projectPath = projectView ? getCopyTargetPath(projectView) : void 0;
2177
2249
  if (!projectPath) {
2178
- setHint("No project to copy");
2250
+ setHint(`No project to ${actionLabel}`);
2179
2251
  setTimeout(() => {
2180
2252
  setHint(defaultHintMessage);
2181
2253
  }, 2e3);
2182
2254
  return;
2183
2255
  }
2256
+ if (outputPathOnExit) {
2257
+ onSetExitPath?.(projectPath);
2258
+ exit();
2259
+ return;
2260
+ }
2184
2261
  try {
2185
2262
  const command = buildCdCommand(projectPath);
2186
2263
  clipboard.writeSync(command);
@@ -2193,7 +2270,7 @@ var App = ({
2193
2270
  setTimeout(() => {
2194
2271
  setHint(defaultHintMessage);
2195
2272
  }, 2e3);
2196
- }, [index, sortedProjects]);
2273
+ }, [defaultHintMessage, exit, index, onSetExitPath, outputPathOnExit, sortedProjects]);
2197
2274
  const launchSelected = useCallback(async () => {
2198
2275
  const projectView = sortedProjects[index];
2199
2276
  if (!projectView) {
@@ -2248,7 +2325,7 @@ var App = ({
2248
2325
  setHint(defaultHintMessage);
2249
2326
  }, 3e3);
2250
2327
  }
2251
- }, [exit, index, onLaunch, onSetExitPath, outputPathOnExit, sortedProjects]);
2328
+ }, [defaultHintMessage, exit, index, onLaunch, onSetExitPath, outputPathOnExit, sortedProjects]);
2252
2329
  const launchSelectedWithEditor = useCallback(async () => {
2253
2330
  if (!onLaunchWithEditor) {
2254
2331
  setHint("Launch with editor not available");
@@ -2310,7 +2387,7 @@ var App = ({
2310
2387
  setHint(defaultHintMessage);
2311
2388
  }, 3e3);
2312
2389
  }
2313
- }, [exit, index, onLaunchWithEditor, onSetExitPath, outputPathOnExit, sortedProjects]);
2390
+ }, [defaultHintMessage, exit, index, onLaunchWithEditor, onSetExitPath, outputPathOnExit, sortedProjects]);
2314
2391
  const launchEditorOnly = useCallback(async () => {
2315
2392
  if (!onLaunchEditorOnly) {
2316
2393
  setHint("Launch editor only not available");
@@ -2341,7 +2418,7 @@ var App = ({
2341
2418
  setHint(defaultHintMessage);
2342
2419
  }, 3e3);
2343
2420
  }
2344
- }, [index, onLaunchEditorOnly, sortedProjects]);
2421
+ }, [defaultHintMessage, index, onLaunchEditorOnly, sortedProjects]);
2345
2422
  const terminateSelected = useCallback(async () => {
2346
2423
  const projectView = sortedProjects[index];
2347
2424
  if (!projectView) {
@@ -2384,7 +2461,7 @@ var App = ({
2384
2461
  setHint(defaultHintMessage);
2385
2462
  }, 3e3);
2386
2463
  }
2387
- }, [index, onTerminate, sortedProjects]);
2464
+ }, [defaultHintMessage, index, onTerminate, sortedProjects]);
2388
2465
  useEffect4(() => {
2389
2466
  setProjectViews(projects);
2390
2467
  setReleasedProjects(/* @__PURE__ */ new Set());
@@ -2442,7 +2519,44 @@ var App = ({
2442
2519
  } finally {
2443
2520
  setIsRefreshing(false);
2444
2521
  }
2445
- }, [isRefreshing, onRefresh, sortedProjects]);
2522
+ }, [defaultHintMessage, isRefreshing, onRefresh, sortedProjects]);
2523
+ const toggleFavoriteSelected = useCallback(async () => {
2524
+ if (!onToggleFavorite) {
2525
+ setHint("Toggle favorite not available");
2526
+ setTimeout(() => {
2527
+ setHint(defaultHintMessage);
2528
+ }, 2e3);
2529
+ return;
2530
+ }
2531
+ const projectView = sortedProjects[index];
2532
+ if (!projectView) {
2533
+ setHint("No project to toggle favorite");
2534
+ setTimeout(() => {
2535
+ setHint(defaultHintMessage);
2536
+ }, 2e3);
2537
+ return;
2538
+ }
2539
+ const { project } = projectView;
2540
+ try {
2541
+ const newFavorite = await onToggleFavorite(project);
2542
+ setProjectViews(
2543
+ (prev) => prev.map(
2544
+ (pv) => pv.project.id === project.id ? { ...pv, project: { ...pv.project, favorite: newFavorite } } : pv
2545
+ )
2546
+ );
2547
+ const status = newFavorite ? "added to" : "removed from";
2548
+ setHint(`${project.title} ${status} favorites`);
2549
+ setTimeout(() => {
2550
+ setHint(defaultHintMessage);
2551
+ }, 2e3);
2552
+ } catch (error) {
2553
+ const message = error instanceof Error ? error.message : String(error);
2554
+ setHint(`Failed to toggle favorite: ${message}`);
2555
+ setTimeout(() => {
2556
+ setHint(defaultHintMessage);
2557
+ }, 3e3);
2558
+ }
2559
+ }, [defaultHintMessage, index, onToggleFavorite, sortedProjects]);
2446
2560
  useInput((input, key) => {
2447
2561
  if (isSortMenuOpen) {
2448
2562
  if (key.escape || input === "\x1B") {
@@ -2563,6 +2677,10 @@ var App = ({
2563
2677
  }
2564
2678
  if (input === "c") {
2565
2679
  copyProjectPath();
2680
+ return;
2681
+ }
2682
+ if (input === "f" || input === "F") {
2683
+ void toggleFavoriteSelected();
2566
2684
  }
2567
2685
  });
2568
2686
  const { startIndex, visibleProjects } = useMemo2(() => {
@@ -2642,6 +2760,12 @@ var App = ({
2642
2760
  import { jsx as jsx7 } from "react/jsx-runtime";
2643
2761
  var SHELL_INIT_MARKER_START = "# >>> unity-hub-cli >>>";
2644
2762
  var SHELL_INIT_MARKER_END = "# <<< unity-hub-cli <<<";
2763
+ var getVersion = () => {
2764
+ const currentDir = dirname2(fileURLToPath(import.meta.url));
2765
+ const packageJsonPath = join9(currentDir, "..", "package.json");
2766
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2767
+ return packageJson.version;
2768
+ };
2645
2769
  var getShellConfigPath = () => {
2646
2770
  const shell = process3.env["SHELL"] ?? "";
2647
2771
  const home = homedir();
@@ -2743,6 +2867,139 @@ var getUnityHubCliPath = () => {
2743
2867
  }
2744
2868
  return "unity-hub-cli";
2745
2869
  };
2870
+ var getNpmCommand = () => {
2871
+ return process3.platform === "win32" ? "npm.cmd" : "npm";
2872
+ };
2873
+ var NPM_VIEW_TIMEOUT_MS = 3e4;
2874
+ var NPM_INSTALL_TIMEOUT_MS = 10 * 6e4;
2875
+ var parseSemver = (version) => {
2876
+ const trimmed = version.trim().replace(/^v/i, "");
2877
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/.exec(trimmed);
2878
+ if (!match) {
2879
+ return void 0;
2880
+ }
2881
+ const major = Number(match[1]);
2882
+ const minor = Number(match[2]);
2883
+ const patch = Number(match[3]);
2884
+ if (!Number.isFinite(major) || !Number.isFinite(minor) || !Number.isFinite(patch)) {
2885
+ return void 0;
2886
+ }
2887
+ const prereleaseRaw = match[4];
2888
+ const prerelease = prereleaseRaw ? prereleaseRaw.split(".").map((identifier) => {
2889
+ const numeric = /^[0-9]+$/.test(identifier) ? Number(identifier) : void 0;
2890
+ return typeof numeric === "number" && Number.isFinite(numeric) ? numeric : identifier;
2891
+ }) : [];
2892
+ return { major, minor, patch, prerelease };
2893
+ };
2894
+ var compareSemverIdentifiers = (a, b) => {
2895
+ if (typeof a === "number" && typeof b === "number") {
2896
+ return a === b ? 0 : a < b ? -1 : 1;
2897
+ }
2898
+ if (typeof a === "number" && typeof b === "string") {
2899
+ return -1;
2900
+ }
2901
+ if (typeof a === "string" && typeof b === "number") {
2902
+ return 1;
2903
+ }
2904
+ const aStr = String(a);
2905
+ const bStr = String(b);
2906
+ if (aStr === bStr) {
2907
+ return 0;
2908
+ }
2909
+ return aStr < bStr ? -1 : 1;
2910
+ };
2911
+ var compareSemver = (a, b) => {
2912
+ if (a.major !== b.major) {
2913
+ return a.major < b.major ? -1 : 1;
2914
+ }
2915
+ if (a.minor !== b.minor) {
2916
+ return a.minor < b.minor ? -1 : 1;
2917
+ }
2918
+ if (a.patch !== b.patch) {
2919
+ return a.patch < b.patch ? -1 : 1;
2920
+ }
2921
+ const aHasPre = a.prerelease.length > 0;
2922
+ const bHasPre = b.prerelease.length > 0;
2923
+ if (!aHasPre && !bHasPre) {
2924
+ return 0;
2925
+ }
2926
+ if (!aHasPre && bHasPre) {
2927
+ return 1;
2928
+ }
2929
+ if (aHasPre && !bHasPre) {
2930
+ return -1;
2931
+ }
2932
+ const length = Math.max(a.prerelease.length, b.prerelease.length);
2933
+ for (let i = 0; i < length; i += 1) {
2934
+ const aId = a.prerelease[i];
2935
+ const bId = b.prerelease[i];
2936
+ if (aId === void 0 && bId === void 0) {
2937
+ return 0;
2938
+ }
2939
+ if (aId === void 0) {
2940
+ return -1;
2941
+ }
2942
+ if (bId === void 0) {
2943
+ return 1;
2944
+ }
2945
+ const cmp = compareSemverIdentifiers(aId, bId);
2946
+ if (cmp !== 0) {
2947
+ return cmp;
2948
+ }
2949
+ }
2950
+ return 0;
2951
+ };
2952
+ var readLatestVersionFromNpm = () => {
2953
+ const npmCommand = getNpmCommand();
2954
+ const result = spawnSync(npmCommand, ["view", "unity-hub-cli", "version"], {
2955
+ encoding: "utf-8",
2956
+ stdio: ["ignore", "pipe", "pipe"],
2957
+ timeout: NPM_VIEW_TIMEOUT_MS
2958
+ });
2959
+ if (result.signal) {
2960
+ return { ok: false, reason: "timeout" };
2961
+ }
2962
+ if (result.error) {
2963
+ const errorCode = result.error.code;
2964
+ if (errorCode === "ETIMEDOUT") {
2965
+ return { ok: false, reason: "timeout" };
2966
+ }
2967
+ return { ok: false, reason: "spawn_failed" };
2968
+ }
2969
+ if (typeof result.status === "number" && result.status !== 0) {
2970
+ return { ok: false, reason: "nonzero_exit" };
2971
+ }
2972
+ const version = result.stdout.trim();
2973
+ if (!version) {
2974
+ return { ok: false, reason: "empty_output" };
2975
+ }
2976
+ return { ok: true, version };
2977
+ };
2978
+ var installLatestVersionGlobally = () => {
2979
+ const npmCommand = getNpmCommand();
2980
+ const result = spawnSync(
2981
+ npmCommand,
2982
+ ["install", "-g", "unity-hub-cli@latest", "--ignore-scripts", "--no-fund"],
2983
+ {
2984
+ stdio: "inherit",
2985
+ timeout: NPM_INSTALL_TIMEOUT_MS
2986
+ }
2987
+ );
2988
+ if (result.signal) {
2989
+ return { ok: false, reason: "timeout" };
2990
+ }
2991
+ if (result.error) {
2992
+ const errorCode = result.error.code;
2993
+ if (errorCode === "ETIMEDOUT") {
2994
+ return { ok: false, reason: "timeout" };
2995
+ }
2996
+ return { ok: false, reason: "spawn_failed" };
2997
+ }
2998
+ if (typeof result.status === "number" && result.status !== 0) {
2999
+ return { ok: false, reason: "nonzero_exit" };
3000
+ }
3001
+ return { ok: true };
3002
+ };
2746
3003
  var getShellInitScript = () => {
2747
3004
  const shell = process3.env["SHELL"] ?? "";
2748
3005
  const isWindows = process3.platform === "win32";
@@ -2793,6 +3050,73 @@ end`;
2793
3050
  };
2794
3051
  var bootstrap = async () => {
2795
3052
  const args = process3.argv.slice(2);
3053
+ if (args.includes("-v") || args.includes("--version")) {
3054
+ console.log(getVersion());
3055
+ return;
3056
+ }
3057
+ if (args[0] === "update") {
3058
+ const currentVersion = getVersion();
3059
+ const latestVersionResult = readLatestVersionFromNpm();
3060
+ if (!latestVersionResult.ok) {
3061
+ if (latestVersionResult.reason === "timeout") {
3062
+ console.error("Failed to read the latest version from npm (timeout). Please check your network/proxy settings and try again.");
3063
+ } else if (latestVersionResult.reason === "spawn_failed") {
3064
+ console.error("Failed to run npm. Please ensure npm is installed and available in your PATH.");
3065
+ } else {
3066
+ console.error("Failed to read the latest version from npm. Please ensure you can access the npm registry.");
3067
+ }
3068
+ process3.exitCode = 1;
3069
+ return;
3070
+ }
3071
+ const latestVersion = latestVersionResult.version;
3072
+ const parsedCurrent = parseSemver(currentVersion);
3073
+ const parsedLatest = parseSemver(latestVersion);
3074
+ if (parsedCurrent && parsedLatest) {
3075
+ const cmp = compareSemver(parsedLatest, parsedCurrent);
3076
+ if (cmp <= 0) {
3077
+ console.log(`Already up to date: ${currentVersion}`);
3078
+ if (cmp < 0) {
3079
+ console.log(`Current version is newer than npm latest: ${latestVersion}`);
3080
+ }
3081
+ return;
3082
+ }
3083
+ } else if (latestVersion === currentVersion) {
3084
+ console.log(`Already up to date: ${currentVersion}`);
3085
+ return;
3086
+ }
3087
+ console.log(`Current version: ${currentVersion}`);
3088
+ console.log(`Latest version: ${latestVersion}`);
3089
+ console.log("This will update the global installation via npm: npm install -g unity-hub-cli@latest --ignore-scripts --no-fund");
3090
+ console.log("");
3091
+ if (!process3.stdin.isTTY) {
3092
+ console.error("Interactive confirmation is not available in this environment (stdin is not a TTY).");
3093
+ process3.exitCode = 1;
3094
+ return;
3095
+ }
3096
+ if (!parsedCurrent || !parsedLatest) {
3097
+ console.log("Warning: could not compare versions reliably; proceeding may downgrade your installation.");
3098
+ console.log("");
3099
+ }
3100
+ const confirmed = await askConfirmation("Proceed with update? (y/n): ");
3101
+ if (!confirmed) {
3102
+ console.log("Update cancelled.");
3103
+ return;
3104
+ }
3105
+ const installResult = installLatestVersionGlobally();
3106
+ if (!installResult.ok) {
3107
+ if (installResult.reason === "timeout") {
3108
+ console.error("Update failed (timeout). Please check your network/proxy settings and try again.");
3109
+ } else if (installResult.reason === "spawn_failed") {
3110
+ console.error("Failed to run npm. Please ensure npm is installed and available in your PATH.");
3111
+ } else {
3112
+ console.error("Update failed. Please check the npm output above.");
3113
+ }
3114
+ process3.exitCode = 1;
3115
+ return;
3116
+ }
3117
+ console.log(`Updated to: ${latestVersion}`);
3118
+ return;
3119
+ }
2796
3120
  if (args.includes("--shell-init")) {
2797
3121
  const isDryRun = args.includes("--dry-run");
2798
3122
  if (isDryRun) {
@@ -2809,6 +3133,11 @@ var bootstrap = async () => {
2809
3133
  `);
2810
3134
  previewShellInit();
2811
3135
  console.log("");
3136
+ if (!process3.stdin.isTTY) {
3137
+ console.error("Interactive confirmation is not available in this environment (stdin is not a TTY).");
3138
+ process3.exitCode = 1;
3139
+ return;
3140
+ }
2812
3141
  const confirmed = await askConfirmation("Proceed with installation? (y/n): ");
2813
3142
  if (!confirmed) {
2814
3143
  console.log("Installation cancelled.");
@@ -2896,6 +3225,7 @@ var bootstrap = async () => {
2896
3225
  onLaunchEditorOnly: (project) => launchEditorOnlyUseCase.execute(project),
2897
3226
  onTerminate: (project) => terminateProjectUseCase.execute(project),
2898
3227
  onRefresh: () => listProjectsUseCase.execute(),
3228
+ onToggleFavorite: (project) => unityHubReader.toggleFavorite(project.path),
2899
3229
  useGitRootName,
2900
3230
  outputPathOnExit,
2901
3231
  onSetExitPath: (path) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unity-hub-cli",
3
- "version": "0.18.0",
3
+ "version": "0.20.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": {
@@ -64,7 +64,7 @@
64
64
  "eslint-config-prettier": "10.1.8",
65
65
  "eslint-import-resolver-typescript": "4.4.4",
66
66
  "eslint-plugin-import": "2.32.0",
67
- "prettier": "3.7.3",
67
+ "prettier": "3.7.4",
68
68
  "tsup": "8.5.1",
69
69
  "tsx": "4.21.0",
70
70
  "typescript": "5.9.3",