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.
- package/dist/index.js +132 -74
- 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
|
-
*
|
|
301
|
-
*
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
325
|
-
if (
|
|
326
|
-
return
|
|
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(
|
|
340
|
-
"
|
|
341
|
-
REGISTRY_PATH,
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
*
|
|
365
|
-
*
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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(
|
|
1240
|
-
"
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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(
|
|
1262
|
-
"
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
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
|
|
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 === "
|
|
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.
|
|
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": "
|
|
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
|
}
|