unity-hub-cli 0.19.0 → 0.21.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 +224 -13
  3. package/package.json +1 -1
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,7 +1,7 @@
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";
@@ -2059,11 +2059,11 @@ var extractRootFolder2 = (repository) => {
2059
2059
  return base || void 0;
2060
2060
  };
2061
2061
  var minimumVisibleProjectCount = 4;
2062
- var defaultHintMessage = `j/k Select \xB7 [o]pen [O]+Editor [i]de [q]uit [r]efresh [c]opy [f]av [s]ort [v]isibility \xB7 ^C Exit`;
2063
- var getCopyTargetPath = (view) => {
2064
- const root = view.repository?.root;
2065
- 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`;
2066
2065
  };
2066
+ var getCopyTargetPath = (view) => view.project.path;
2067
2067
  var App = ({
2068
2068
  projects,
2069
2069
  onLaunch,
@@ -2079,6 +2079,10 @@ var App = ({
2079
2079
  const { exit } = useApp();
2080
2080
  const { stdout } = useStdout2();
2081
2081
  const colors = useThemeColors();
2082
+ const defaultHintMessage = useMemo2(
2083
+ () => buildDefaultHintMessage(outputPathOnExit),
2084
+ [outputPathOnExit]
2085
+ );
2082
2086
  const [projectViews, setProjectViews] = useState4(projects);
2083
2087
  const [isSortMenuOpen, setIsSortMenuOpen] = useState4(false);
2084
2088
  const [isVisibilityMenuOpen, setIsVisibilityMenuOpen] = useState4(false);
@@ -2239,15 +2243,21 @@ var App = ({
2239
2243
  });
2240
2244
  }, [index, limit, sortedProjects.length]);
2241
2245
  const copyProjectPath = useCallback(() => {
2246
+ const actionLabel = outputPathOnExit ? "cd" : "copy";
2242
2247
  const projectView = sortedProjects[index];
2243
2248
  const projectPath = projectView ? getCopyTargetPath(projectView) : void 0;
2244
2249
  if (!projectPath) {
2245
- setHint("No project to copy");
2250
+ setHint(`No project to ${actionLabel}`);
2246
2251
  setTimeout(() => {
2247
2252
  setHint(defaultHintMessage);
2248
2253
  }, 2e3);
2249
2254
  return;
2250
2255
  }
2256
+ if (outputPathOnExit) {
2257
+ onSetExitPath?.(projectPath);
2258
+ exit();
2259
+ return;
2260
+ }
2251
2261
  try {
2252
2262
  const command = buildCdCommand(projectPath);
2253
2263
  clipboard.writeSync(command);
@@ -2260,7 +2270,7 @@ var App = ({
2260
2270
  setTimeout(() => {
2261
2271
  setHint(defaultHintMessage);
2262
2272
  }, 2e3);
2263
- }, [index, sortedProjects]);
2273
+ }, [defaultHintMessage, exit, index, onSetExitPath, outputPathOnExit, sortedProjects]);
2264
2274
  const launchSelected = useCallback(async () => {
2265
2275
  const projectView = sortedProjects[index];
2266
2276
  if (!projectView) {
@@ -2315,7 +2325,7 @@ var App = ({
2315
2325
  setHint(defaultHintMessage);
2316
2326
  }, 3e3);
2317
2327
  }
2318
- }, [exit, index, onLaunch, onSetExitPath, outputPathOnExit, sortedProjects]);
2328
+ }, [defaultHintMessage, exit, index, onLaunch, onSetExitPath, outputPathOnExit, sortedProjects]);
2319
2329
  const launchSelectedWithEditor = useCallback(async () => {
2320
2330
  if (!onLaunchWithEditor) {
2321
2331
  setHint("Launch with editor not available");
@@ -2377,7 +2387,7 @@ var App = ({
2377
2387
  setHint(defaultHintMessage);
2378
2388
  }, 3e3);
2379
2389
  }
2380
- }, [exit, index, onLaunchWithEditor, onSetExitPath, outputPathOnExit, sortedProjects]);
2390
+ }, [defaultHintMessage, exit, index, onLaunchWithEditor, onSetExitPath, outputPathOnExit, sortedProjects]);
2381
2391
  const launchEditorOnly = useCallback(async () => {
2382
2392
  if (!onLaunchEditorOnly) {
2383
2393
  setHint("Launch editor only not available");
@@ -2408,7 +2418,7 @@ var App = ({
2408
2418
  setHint(defaultHintMessage);
2409
2419
  }, 3e3);
2410
2420
  }
2411
- }, [index, onLaunchEditorOnly, sortedProjects]);
2421
+ }, [defaultHintMessage, index, onLaunchEditorOnly, sortedProjects]);
2412
2422
  const terminateSelected = useCallback(async () => {
2413
2423
  const projectView = sortedProjects[index];
2414
2424
  if (!projectView) {
@@ -2451,7 +2461,7 @@ var App = ({
2451
2461
  setHint(defaultHintMessage);
2452
2462
  }, 3e3);
2453
2463
  }
2454
- }, [index, onTerminate, sortedProjects]);
2464
+ }, [defaultHintMessage, index, onTerminate, sortedProjects]);
2455
2465
  useEffect4(() => {
2456
2466
  setProjectViews(projects);
2457
2467
  setReleasedProjects(/* @__PURE__ */ new Set());
@@ -2509,7 +2519,7 @@ var App = ({
2509
2519
  } finally {
2510
2520
  setIsRefreshing(false);
2511
2521
  }
2512
- }, [isRefreshing, onRefresh, sortedProjects]);
2522
+ }, [defaultHintMessage, isRefreshing, onRefresh, sortedProjects]);
2513
2523
  const toggleFavoriteSelected = useCallback(async () => {
2514
2524
  if (!onToggleFavorite) {
2515
2525
  setHint("Toggle favorite not available");
@@ -2546,7 +2556,7 @@ var App = ({
2546
2556
  setHint(defaultHintMessage);
2547
2557
  }, 3e3);
2548
2558
  }
2549
- }, [index, onToggleFavorite, sortedProjects]);
2559
+ }, [defaultHintMessage, index, onToggleFavorite, sortedProjects]);
2550
2560
  useInput((input, key) => {
2551
2561
  if (isSortMenuOpen) {
2552
2562
  if (key.escape || input === "\x1B") {
@@ -2857,6 +2867,139 @@ var getUnityHubCliPath = () => {
2857
2867
  }
2858
2868
  return "unity-hub-cli";
2859
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
+ };
2860
3003
  var getShellInitScript = () => {
2861
3004
  const shell = process3.env["SHELL"] ?? "";
2862
3005
  const isWindows = process3.platform === "win32";
@@ -2911,6 +3054,69 @@ var bootstrap = async () => {
2911
3054
  console.log(getVersion());
2912
3055
  return;
2913
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
+ }
2914
3120
  if (args.includes("--shell-init")) {
2915
3121
  const isDryRun = args.includes("--dry-run");
2916
3122
  if (isDryRun) {
@@ -2927,6 +3133,11 @@ var bootstrap = async () => {
2927
3133
  `);
2928
3134
  previewShellInit();
2929
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
+ }
2930
3141
  const confirmed = await askConfirmation("Proceed with installation? (y/n): ");
2931
3142
  if (!confirmed) {
2932
3143
  console.log("Installation cancelled.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unity-hub-cli",
3
- "version": "0.19.0",
3
+ "version": "0.21.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": {