unity-hub-cli 0.15.0 → 0.16.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 +132 -74
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -294,18 +294,28 @@ var MacExternalEditorPathReader = class {
294
294
  return { status: "found", path: configuredPath, name };
295
295
  }
296
296
  };
297
+ var slnPreferringEditors = ["rider", "visual studio"];
298
+ var prefersSlnFile = (editorPath) => {
299
+ const editorName = basename(editorPath, ".app").toLowerCase();
300
+ return slnPreferringEditors.some((name) => editorName.includes(name));
301
+ };
297
302
  var MacExternalEditorLauncher = class {
298
303
  /**
299
304
  * Launches the external editor with the specified project root.
300
- * If a .sln file exists with the project name, it will be opened directly.
301
- * This allows Rider to open the solution without showing a selection dialog.
305
+ * For Rider/Visual Studio: opens .sln file directly if it exists.
306
+ * For VS Code/Cursor and others: opens the project folder.
302
307
  * @param editorPath - The path to the editor application.
303
308
  * @param projectRoot - The project root directory to open.
304
309
  */
305
310
  async launch(editorPath, projectRoot) {
306
- const projectName = basename(projectRoot);
307
- const slnFilePath = join3(projectRoot, `${projectName}.sln`);
308
- const targetPath = existsSync(slnFilePath) ? slnFilePath : projectRoot;
311
+ let targetPath = projectRoot;
312
+ if (prefersSlnFile(editorPath)) {
313
+ const projectName = basename(projectRoot);
314
+ const slnFilePath = join3(projectRoot, `${projectName}.sln`);
315
+ if (existsSync(slnFilePath)) {
316
+ targetPath = slnFilePath;
317
+ }
318
+ }
309
319
  await execFileAsync("open", ["-a", editorPath, targetPath]);
310
320
  }
311
321
  };
@@ -316,14 +326,77 @@ import { constants as constants4, existsSync as existsSync2 } from "fs";
316
326
  import { access as access4 } from "fs/promises";
317
327
  import { basename as basename2, join as join4 } from "path";
318
328
  import { promisify as promisify2 } from "util";
329
+
330
+ // src/presentation/utils/path.ts
331
+ var homeDirectory = process.env.HOME ?? process.env.USERPROFILE ?? "";
332
+ var normalizedHomeDirectory = homeDirectory.replace(/\\/g, "/");
333
+ var homePrefix = normalizedHomeDirectory ? `${normalizedHomeDirectory}/` : "";
334
+ var isGitBashEnvironment = () => {
335
+ if (process.platform !== "win32") {
336
+ return false;
337
+ }
338
+ return Boolean(process.env.MSYSTEM);
339
+ };
340
+ var getMsysDisabledEnv = () => {
341
+ if (!isGitBashEnvironment()) {
342
+ return process.env;
343
+ }
344
+ return {
345
+ ...process.env,
346
+ MSYS_NO_PATHCONV: "1",
347
+ MSYS2_ARG_CONV_EXCL: "*"
348
+ };
349
+ };
350
+ var shortenHomePath = (targetPath) => {
351
+ if (!normalizedHomeDirectory) {
352
+ return targetPath;
353
+ }
354
+ const normalizedTarget = targetPath.replace(/\\/g, "/");
355
+ if (normalizedTarget === normalizedHomeDirectory) {
356
+ return "~";
357
+ }
358
+ if (homePrefix && normalizedTarget.startsWith(homePrefix)) {
359
+ return `~/${normalizedTarget.slice(homePrefix.length)}`;
360
+ }
361
+ return targetPath;
362
+ };
363
+ var buildCdCommand = (targetPath) => {
364
+ if (process.platform === "win32") {
365
+ if (isGitBashEnvironment()) {
366
+ const msysPath = targetPath.replace(/^([A-Za-z]):[\\/]/, (_, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, "/");
367
+ const escapedForPosix2 = msysPath.replace(/'/g, "'\\''");
368
+ return `cd '${escapedForPosix2}'`;
369
+ }
370
+ const escapedForWindows = targetPath.replace(/"/g, '""');
371
+ return `cd "${escapedForWindows}"`;
372
+ }
373
+ const escapedForPosix = targetPath.replace(/'/g, "'\\''");
374
+ return `cd '${escapedForPosix}'`;
375
+ };
376
+
377
+ // src/infrastructure/externalEditor.win.ts
319
378
  var execFileAsync2 = promisify2(execFile2);
320
379
  var REGISTRY_PATH = "HKEY_CURRENT_USER\\Software\\Unity Technologies\\Unity Editor 5.x";
380
+ var decodeRegBinary = (hex) => {
381
+ const bytes = [];
382
+ for (let i = 0; i < hex.length; i += 2) {
383
+ const byte = parseInt(hex.slice(i, i + 2), 16);
384
+ if (byte !== 0) {
385
+ bytes.push(byte);
386
+ }
387
+ }
388
+ return Buffer.from(bytes).toString("utf8");
389
+ };
321
390
  var parseRegistryOutput = (stdout) => {
322
391
  const lines = stdout.split("\n");
323
392
  for (const line of lines) {
324
- const match = line.match(/kScriptsDefaultApp[^\s]*\s+REG_SZ\s+(.+)/i);
325
- if (match?.[1]) {
326
- return match[1].trim();
393
+ const szMatch = line.match(/kScriptsDefaultApp[^\s]*\s+REG_SZ\s+(.+)/i);
394
+ if (szMatch?.[1]) {
395
+ return szMatch[1].trim();
396
+ }
397
+ const binaryMatch = line.match(/kScriptsDefaultApp[^\s]*\s+REG_BINARY\s+([0-9A-Fa-f]+)/i);
398
+ if (binaryMatch?.[1]) {
399
+ return decodeRegBinary(binaryMatch[1]);
327
400
  }
328
401
  }
329
402
  return void 0;
@@ -336,12 +409,11 @@ var WinExternalEditorPathReader = class {
336
409
  async read() {
337
410
  let configuredPath;
338
411
  try {
339
- const result = await execFileAsync2("reg", [
340
- "query",
341
- REGISTRY_PATH,
342
- "/v",
343
- "kScriptsDefaultApp"
344
- ]);
412
+ const result = await execFileAsync2(
413
+ "reg",
414
+ ["query", REGISTRY_PATH],
415
+ { env: getMsysDisabledEnv() }
416
+ );
345
417
  configuredPath = parseRegistryOutput(result.stdout);
346
418
  } catch {
347
419
  return { status: "not_configured" };
@@ -358,22 +430,33 @@ var WinExternalEditorPathReader = class {
358
430
  return { status: "found", path: configuredPath, name };
359
431
  }
360
432
  };
433
+ var slnPreferringEditors2 = ["rider", "devenv", "visualstudio"];
434
+ var prefersSlnFile2 = (editorPath) => {
435
+ const editorName = basename2(editorPath, ".exe").toLowerCase();
436
+ return slnPreferringEditors2.some((name) => editorName.includes(name));
437
+ };
361
438
  var WinExternalEditorLauncher = class {
362
439
  /**
363
440
  * Launches the external editor with the specified project root.
364
- * If a .sln file exists with the project name, it will be opened directly.
365
- * This allows Rider to open the solution without showing a selection dialog.
441
+ * For Rider/Visual Studio: opens .sln file directly if it exists.
442
+ * For VS Code/Cursor and others: opens the project folder.
366
443
  * @param editorPath - The path to the editor executable.
367
444
  * @param projectRoot - The project root directory to open.
368
445
  */
369
446
  async launch(editorPath, projectRoot) {
370
- const projectName = basename2(projectRoot);
371
- const slnFilePath = join4(projectRoot, `${projectName}.sln`);
372
- const targetPath = existsSync2(slnFilePath) ? slnFilePath : projectRoot;
447
+ let targetPath = projectRoot;
448
+ if (prefersSlnFile2(editorPath)) {
449
+ const projectName = basename2(projectRoot);
450
+ const slnFilePath = join4(projectRoot, `${projectName}.sln`);
451
+ if (existsSync2(slnFilePath)) {
452
+ targetPath = slnFilePath;
453
+ }
454
+ }
373
455
  await new Promise((resolve4, reject) => {
374
456
  const child = spawn(editorPath, [targetPath], {
375
457
  detached: true,
376
- stdio: "ignore"
458
+ stdio: "ignore",
459
+ env: getMsysDisabledEnv()
377
460
  });
378
461
  const handleError = (error) => {
379
462
  child.off("spawn", handleSpawn);
@@ -478,7 +561,8 @@ var NodeProcessLauncher = class {
478
561
  await new Promise((resolve4, reject) => {
479
562
  const child = spawn2(command, args, {
480
563
  detached,
481
- stdio: "ignore"
564
+ stdio: "ignore",
565
+ env: getMsysDisabledEnv()
482
566
  });
483
567
  const handleError = (error) => {
484
568
  child.off("spawn", handleSpawn);
@@ -1209,7 +1293,7 @@ var WinUnityProcessReader = class {
1209
1293
  "-Command",
1210
1294
  psCommand
1211
1295
  ],
1212
- { encoding: "utf8" }
1296
+ { encoding: "utf8", env: getMsysDisabledEnv() }
1213
1297
  );
1214
1298
  stdout = (result.stdout ?? "").trim();
1215
1299
  } catch (error) {
@@ -1236,14 +1320,18 @@ var WinUnityProcessReader = class {
1236
1320
  var WinUnityProcessTerminator = class {
1237
1321
  async terminate(unityProcess) {
1238
1322
  try {
1239
- await execFileAsync5("powershell.exe", [
1240
- "-NoProfile",
1241
- "-NonInteractive",
1242
- "-ExecutionPolicy",
1243
- "Bypass",
1244
- "-Command",
1245
- `Stop-Process -Id ${unityProcess.pid}`
1246
- ]);
1323
+ await execFileAsync5(
1324
+ "powershell.exe",
1325
+ [
1326
+ "-NoProfile",
1327
+ "-NonInteractive",
1328
+ "-ExecutionPolicy",
1329
+ "Bypass",
1330
+ "-Command",
1331
+ `Stop-Process -Id ${unityProcess.pid}`
1332
+ ],
1333
+ { env: getMsysDisabledEnv() }
1334
+ );
1247
1335
  } catch (error) {
1248
1336
  if (!ensureProcessAlive2(unityProcess.pid)) {
1249
1337
  return { terminated: true, stage: "sigterm" };
@@ -1258,14 +1346,18 @@ var WinUnityProcessTerminator = class {
1258
1346
  }
1259
1347
  }
1260
1348
  try {
1261
- await execFileAsync5("powershell.exe", [
1262
- "-NoProfile",
1263
- "-NonInteractive",
1264
- "-ExecutionPolicy",
1265
- "Bypass",
1266
- "-Command",
1267
- `Stop-Process -Id ${unityProcess.pid} -Force`
1268
- ]);
1349
+ await execFileAsync5(
1350
+ "powershell.exe",
1351
+ [
1352
+ "-NoProfile",
1353
+ "-NonInteractive",
1354
+ "-ExecutionPolicy",
1355
+ "Bypass",
1356
+ "-Command",
1357
+ `Stop-Process -Id ${unityProcess.pid} -Force`
1358
+ ],
1359
+ { env: getMsysDisabledEnv() }
1360
+ );
1269
1361
  } catch (error) {
1270
1362
  if (!ensureProcessAlive2(unityProcess.pid)) {
1271
1363
  return { terminated: true, stage: "sigkill" };
@@ -1395,39 +1487,6 @@ var ThemeProvider = ({ theme, children }) => {
1395
1487
  return createElement(ThemeContext.Provider, { value }, children);
1396
1488
  };
1397
1489
 
1398
- // src/presentation/utils/path.ts
1399
- var homeDirectory = process.env.HOME ?? process.env.USERPROFILE ?? "";
1400
- var normalizedHomeDirectory = homeDirectory.replace(/\\/g, "/");
1401
- var homePrefix = normalizedHomeDirectory ? `${normalizedHomeDirectory}/` : "";
1402
- var shortenHomePath = (targetPath) => {
1403
- if (!normalizedHomeDirectory) {
1404
- return targetPath;
1405
- }
1406
- const normalizedTarget = targetPath.replace(/\\/g, "/");
1407
- if (normalizedTarget === normalizedHomeDirectory) {
1408
- return "~";
1409
- }
1410
- if (homePrefix && normalizedTarget.startsWith(homePrefix)) {
1411
- return `~/${normalizedTarget.slice(homePrefix.length)}`;
1412
- }
1413
- return targetPath;
1414
- };
1415
- var buildCdCommand = (targetPath) => {
1416
- if (process.platform === "win32") {
1417
- const isGitBash = Boolean(process.env.MSYSTEM) || /bash/i.test(process.env.SHELL ?? "");
1418
- if (isGitBash) {
1419
- const windowsPath = targetPath;
1420
- const msysPath = windowsPath.replace(/^([A-Za-z]):[\\/]/, (_, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, "/");
1421
- const escapedForPosix2 = msysPath.replace(/'/g, "'\\''");
1422
- return `cd '${escapedForPosix2}'`;
1423
- }
1424
- const escapedForWindows = targetPath.replace(/"/g, '""');
1425
- return `cd "${escapedForWindows}"`;
1426
- }
1427
- const escapedForPosix = targetPath.replace(/'/g, "'\\''");
1428
- return `cd '${escapedForPosix}'`;
1429
- };
1430
-
1431
1490
  // src/presentation/components/ProjectRow.tsx
1432
1491
  import { Box as Box2, Text, useStdout } from "ink";
1433
1492
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -1928,8 +1987,7 @@ var extractRootFolder2 = (repository) => {
1928
1987
  return base || void 0;
1929
1988
  };
1930
1989
  var minimumVisibleProjectCount = 4;
1931
- var editorOnlyKey = process2.platform === "darwin" ? "\u2325o" : "Alt+o";
1932
- var defaultHintMessage = `j/k Select \xB7 [o]pen [O]+Editor [${editorOnlyKey}]Editor [q]uit [r]efresh [c]opy [s]ort [v]isibility \xB7 ^C Exit`;
1990
+ 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`;
1933
1991
  var getCopyTargetPath = (view) => {
1934
1992
  const root = view.repository?.root;
1935
1993
  return root && root.length > 0 ? root : view.project.path;
@@ -2481,7 +2539,7 @@ var App = ({
2481
2539
  void terminateSelected();
2482
2540
  return;
2483
2541
  }
2484
- if (input === "\xF8" || input === "o" && key.meta) {
2542
+ if (input === "i") {
2485
2543
  void launchEditorOnly();
2486
2544
  return;
2487
2545
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "unity-hub-cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.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": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
7
+ "test": "vitest run",
8
8
  "dev": "tsx src/index.ts",
9
9
  "build": "tsup",
10
10
  "start": "node dist/index.js",
@@ -55,6 +55,7 @@
55
55
  "prettier": "^3.6.2",
56
56
  "tsup": "^8.5.0",
57
57
  "tsx": "^4.20.6",
58
- "typescript": "^5.9.3"
58
+ "typescript": "^5.9.3",
59
+ "vitest": "^4.0.14"
59
60
  }
60
61
  }