unity-hub-cli 0.12.0 → 0.13.1
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/README.md +13 -2
- package/dist/index.js +432 -47
- 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
|
|
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
|
|
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 =
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
365
|
-
import { access as
|
|
366
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
950
|
+
const tempDirectoryPath = join6(projectPath, TEMP_DIRECTORY_NAME);
|
|
612
951
|
try {
|
|
613
952
|
await rm2(tempDirectoryPath, {
|
|
614
953
|
recursive: true,
|
|
@@ -620,6 +959,7 @@ var UnityTempDirectoryCleaner = class {
|
|
|
620
959
|
};
|
|
621
960
|
|
|
622
961
|
// src/presentation/App.tsx
|
|
962
|
+
import { basename as basename4 } from "path";
|
|
623
963
|
import clipboard from "clipboardy";
|
|
624
964
|
import { Box as Box6, Text as Text4, useApp, useInput, useStdout as useStdout2 } from "ink";
|
|
625
965
|
import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useState as useState4 } from "react";
|
|
@@ -663,6 +1003,7 @@ var LayoutManager = ({
|
|
|
663
1003
|
};
|
|
664
1004
|
|
|
665
1005
|
// src/presentation/components/ProjectList.tsx
|
|
1006
|
+
import { basename as basename3 } from "path";
|
|
666
1007
|
import { Box as Box3 } from "ink";
|
|
667
1008
|
import { useMemo } from "react";
|
|
668
1009
|
|
|
@@ -685,6 +1026,13 @@ var shortenHomePath = (targetPath) => {
|
|
|
685
1026
|
};
|
|
686
1027
|
var buildCdCommand = (targetPath) => {
|
|
687
1028
|
if (process.platform === "win32") {
|
|
1029
|
+
const isGitBash = Boolean(process.env.MSYSTEM) || /bash/i.test(process.env.SHELL ?? "");
|
|
1030
|
+
if (isGitBash) {
|
|
1031
|
+
const windowsPath = targetPath;
|
|
1032
|
+
const msysPath = windowsPath.replace(/^([A-Za-z]):[\\/]/, (_, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, "/");
|
|
1033
|
+
const escapedForPosix2 = msysPath.replace(/'/g, "'\\''");
|
|
1034
|
+
return `cd '${escapedForPosix2}'`;
|
|
1035
|
+
}
|
|
688
1036
|
const escapedForWindows = targetPath.replace(/"/g, '""');
|
|
689
1037
|
return `cd "${escapedForWindows}"`;
|
|
690
1038
|
}
|
|
@@ -708,7 +1056,8 @@ var ProjectRow = ({
|
|
|
708
1056
|
pathLine,
|
|
709
1057
|
showBranch,
|
|
710
1058
|
showPath,
|
|
711
|
-
scrollbar
|
|
1059
|
+
scrollbar,
|
|
1060
|
+
showSpacer
|
|
712
1061
|
}) => {
|
|
713
1062
|
const { stdout } = useStdout();
|
|
714
1063
|
const computedCenterWidth = typeof stdout?.columns === "number" ? Math.max(0, stdout.columns - 6) : void 0;
|
|
@@ -731,13 +1080,13 @@ var ProjectRow = ({
|
|
|
731
1080
|
] }),
|
|
732
1081
|
showBranch ? /* @__PURE__ */ jsx2(Text, { color: "#e3839c", wrap: "truncate", children: branchLine }) : null,
|
|
733
1082
|
showPath ? /* @__PURE__ */ jsx2(Text, { color: "#719bd8", wrap: "truncate", children: pathLine }) : null,
|
|
734
|
-
/* @__PURE__ */ jsx2(Text, { children: " " })
|
|
1083
|
+
showSpacer ? /* @__PURE__ */ jsx2(Text, { children: " " }) : null
|
|
735
1084
|
] }),
|
|
736
1085
|
/* @__PURE__ */ jsxs2(Box2, { marginLeft: 1, width: 1, flexDirection: "column", alignItems: "center", children: [
|
|
737
1086
|
/* @__PURE__ */ jsx2(Text, { children: scrollbar.title }),
|
|
738
1087
|
showBranch ? /* @__PURE__ */ jsx2(Text, { children: scrollbar.branch }) : null,
|
|
739
1088
|
showPath ? /* @__PURE__ */ jsx2(Text, { children: scrollbar.path }) : null,
|
|
740
|
-
/* @__PURE__ */ jsx2(Text, { children: scrollbar.spacer })
|
|
1089
|
+
showSpacer ? /* @__PURE__ */ jsx2(Text, { children: scrollbar.spacer }) : null
|
|
741
1090
|
] })
|
|
742
1091
|
] });
|
|
743
1092
|
};
|
|
@@ -749,17 +1098,14 @@ var LOCK_COLOR = "yellow";
|
|
|
749
1098
|
var STATUS_LABELS = {
|
|
750
1099
|
idle: "",
|
|
751
1100
|
running: "[running]",
|
|
752
|
-
crashed: "
|
|
1101
|
+
crashed: ""
|
|
753
1102
|
};
|
|
754
1103
|
var extractRootFolder = (repository) => {
|
|
755
1104
|
if (!repository?.root) {
|
|
756
1105
|
return void 0;
|
|
757
1106
|
}
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
return void 0;
|
|
761
|
-
}
|
|
762
|
-
return segments[segments.length - 1];
|
|
1107
|
+
const base = basename3(repository.root);
|
|
1108
|
+
return base || void 0;
|
|
763
1109
|
};
|
|
764
1110
|
var formatProjectName = (projectTitle, repository, useGitRootName) => {
|
|
765
1111
|
if (!useGitRootName) {
|
|
@@ -851,7 +1197,7 @@ var ProjectList = ({
|
|
|
851
1197
|
return [];
|
|
852
1198
|
}
|
|
853
1199
|
if (totalLines <= visibleLines) {
|
|
854
|
-
return Array.from({ length: visibleLines }, () => "
|
|
1200
|
+
return Array.from({ length: visibleLines }, () => " ");
|
|
855
1201
|
}
|
|
856
1202
|
const trackLength = visibleLines;
|
|
857
1203
|
const sliderSize = Math.max(1, Math.round(visibleLines / totalLines * trackLength));
|
|
@@ -896,7 +1242,7 @@ var ProjectList = ({
|
|
|
896
1242
|
const pathScrollbar = showPath ? scrollbarChars[baseScrollbarIndex + 1 + (showBranch ? 1 : 0)] ?? " " : " ";
|
|
897
1243
|
const spacerScrollbar = scrollbarChars[baseScrollbarIndex + linesPerProject - 1] ?? " ";
|
|
898
1244
|
const statusLabel = STATUS_LABELS[displayStatus];
|
|
899
|
-
const statusColor = displayStatus === "running" ? LOCK_COLOR :
|
|
1245
|
+
const statusColor = displayStatus === "running" ? LOCK_COLOR : void 0;
|
|
900
1246
|
return /* @__PURE__ */ jsx3(
|
|
901
1247
|
ProjectRow,
|
|
902
1248
|
{
|
|
@@ -917,7 +1263,8 @@ var ProjectList = ({
|
|
|
917
1263
|
branch: branchScrollbar,
|
|
918
1264
|
path: pathScrollbar,
|
|
919
1265
|
spacer: spacerScrollbar
|
|
920
|
-
}
|
|
1266
|
+
},
|
|
1267
|
+
showSpacer: offset < visibleProjects.length - 1
|
|
921
1268
|
},
|
|
922
1269
|
project.id
|
|
923
1270
|
);
|
|
@@ -1015,7 +1362,7 @@ var VisibilityPanel = ({ visibility, focusedIndex, width }) => {
|
|
|
1015
1362
|
import { useEffect, useState } from "react";
|
|
1016
1363
|
|
|
1017
1364
|
// src/infrastructure/config.ts
|
|
1018
|
-
import { mkdir, readFile as
|
|
1365
|
+
import { mkdir, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1019
1366
|
var defaultSortPreferences = {
|
|
1020
1367
|
favoritesFirst: true,
|
|
1021
1368
|
primary: "updated",
|
|
@@ -1030,6 +1377,10 @@ var defaultAppConfig = {
|
|
|
1030
1377
|
visibility: defaultVisibilityPreferences
|
|
1031
1378
|
};
|
|
1032
1379
|
var getConfigDir = () => {
|
|
1380
|
+
if (process.platform === "win32") {
|
|
1381
|
+
const appdata = process.env.APPDATA ?? "";
|
|
1382
|
+
return appdata ? `${appdata}\\UnityHubCli` : "UnityHubCli";
|
|
1383
|
+
}
|
|
1033
1384
|
const home = process.env.HOME ?? "";
|
|
1034
1385
|
return `${home}/Library/Application Support/UnityHubCli`;
|
|
1035
1386
|
};
|
|
@@ -1066,7 +1417,7 @@ var sanitizeAppConfig = (input) => {
|
|
|
1066
1417
|
};
|
|
1067
1418
|
var readAppConfig = async () => {
|
|
1068
1419
|
try {
|
|
1069
|
-
const content = await
|
|
1420
|
+
const content = await readFile4(getConfigPath(), "utf8");
|
|
1070
1421
|
const json = JSON.parse(content);
|
|
1071
1422
|
return sanitizeAppConfig(json);
|
|
1072
1423
|
} catch {
|
|
@@ -1079,7 +1430,7 @@ var writeAppConfig = async (config) => {
|
|
|
1079
1430
|
} catch {
|
|
1080
1431
|
}
|
|
1081
1432
|
const json = JSON.stringify(sanitizeAppConfig(config), void 0, 2);
|
|
1082
|
-
await
|
|
1433
|
+
await writeFile3(getConfigPath(), json, "utf8");
|
|
1083
1434
|
};
|
|
1084
1435
|
var readSortPreferences = async () => {
|
|
1085
1436
|
const config = await readAppConfig();
|
|
@@ -1152,14 +1503,14 @@ var useVisibilityPreferences = () => {
|
|
|
1152
1503
|
|
|
1153
1504
|
// src/presentation/hooks/useVisibleCount.ts
|
|
1154
1505
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
1155
|
-
var useVisibleCount = (stdout, linesPerProject, panelVisible, panelHeight, minimumVisibleProjectCount2) => {
|
|
1506
|
+
var useVisibleCount = (stdout, linesPerProject, panelVisible, panelHeight, minimumVisibleProjectCount2, statusBarRows) => {
|
|
1156
1507
|
const compute = () => {
|
|
1157
1508
|
if (!stdout || typeof stdout.columns !== "number" || typeof stdout.rows !== "number") {
|
|
1158
1509
|
return minimumVisibleProjectCount2;
|
|
1159
1510
|
}
|
|
1160
1511
|
const borderRows = 2;
|
|
1161
|
-
const hintRows = 1;
|
|
1162
|
-
const reservedRows = borderRows + hintRows + (panelVisible ? panelHeight : 0);
|
|
1512
|
+
const hintRows = Math.max(1, statusBarRows);
|
|
1513
|
+
const reservedRows = borderRows + hintRows + (panelVisible ? panelHeight + 1 : 0);
|
|
1163
1514
|
const availableRows = Math.max(0, stdout.rows - reservedRows);
|
|
1164
1515
|
const rowsPerProject = Math.max(linesPerProject, 1);
|
|
1165
1516
|
const calculatedCount = Math.max(1, Math.floor(availableRows / rowsPerProject));
|
|
@@ -1173,7 +1524,7 @@ var useVisibleCount = (stdout, linesPerProject, panelVisible, panelHeight, minim
|
|
|
1173
1524
|
return () => {
|
|
1174
1525
|
stdout?.off("resize", updateVisible);
|
|
1175
1526
|
};
|
|
1176
|
-
}, [stdout, linesPerProject, panelVisible, panelHeight]);
|
|
1527
|
+
}, [stdout, linesPerProject, panelVisible, panelHeight, statusBarRows]);
|
|
1177
1528
|
return visibleCount;
|
|
1178
1529
|
};
|
|
1179
1530
|
|
|
@@ -1183,14 +1534,11 @@ var extractRootFolder2 = (repository) => {
|
|
|
1183
1534
|
if (!repository?.root) {
|
|
1184
1535
|
return void 0;
|
|
1185
1536
|
}
|
|
1186
|
-
const
|
|
1187
|
-
|
|
1188
|
-
return void 0;
|
|
1189
|
-
}
|
|
1190
|
-
return segments[segments.length - 1];
|
|
1537
|
+
const base = basename4(repository.root);
|
|
1538
|
+
return base || void 0;
|
|
1191
1539
|
};
|
|
1192
1540
|
var minimumVisibleProjectCount = 4;
|
|
1193
|
-
var defaultHintMessage = "
|
|
1541
|
+
var defaultHintMessage = "j/k Select \xB7 [o]pen [q]uit [r]efresh [c]opy [s]ort [v]isibility \xB7 ^C Exit";
|
|
1194
1542
|
var getCopyTargetPath = (view) => {
|
|
1195
1543
|
const root = view.repository?.root;
|
|
1196
1544
|
return root && root.length > 0 ? root : view.project.path;
|
|
@@ -1215,7 +1563,6 @@ var App = ({
|
|
|
1215
1563
|
const linesPerProject = (showBranch ? 1 : 0) + (showPath ? 1 : 0) + 2;
|
|
1216
1564
|
const isAnyMenuOpen = isSortMenuOpen || isVisibilityMenuOpen;
|
|
1217
1565
|
const panelHeight = isVisibilityMenuOpen ? visibilityPanelHeight : sortPanelHeight;
|
|
1218
|
-
const visibleCount = useVisibleCount(stdout, linesPerProject, isAnyMenuOpen, panelHeight, minimumVisibleProjectCount);
|
|
1219
1566
|
const [index, setIndex] = useState4(0);
|
|
1220
1567
|
const [hint, setHint] = useState4(defaultHintMessage);
|
|
1221
1568
|
const [windowStart, setWindowStart] = useState4(0);
|
|
@@ -1223,7 +1570,18 @@ var App = ({
|
|
|
1223
1570
|
const [launchedProjects, setLaunchedProjects] = useState4(/* @__PURE__ */ new Set());
|
|
1224
1571
|
const [isRefreshing, setIsRefreshing] = useState4(false);
|
|
1225
1572
|
const [sortMenuIndex, setSortMenuIndex] = useState4(0);
|
|
1573
|
+
const [directionManuallyChanged, setDirectionManuallyChanged] = useState4(false);
|
|
1226
1574
|
const { sortPreferences, setSortPreferences } = useSortPreferences();
|
|
1575
|
+
const columns = typeof stdout?.columns === "number" ? stdout.columns : void 0;
|
|
1576
|
+
const statusBarRows = isAnyMenuOpen ? 1 : Math.max(1, typeof columns === "number" && columns > 0 ? Math.ceil(hint.length / columns) : 1);
|
|
1577
|
+
const visibleCount = useVisibleCount(
|
|
1578
|
+
stdout,
|
|
1579
|
+
linesPerProject,
|
|
1580
|
+
isAnyMenuOpen,
|
|
1581
|
+
panelHeight,
|
|
1582
|
+
minimumVisibleProjectCount,
|
|
1583
|
+
statusBarRows
|
|
1584
|
+
);
|
|
1227
1585
|
const clearScreen = useCallback(() => {
|
|
1228
1586
|
stdout?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
1229
1587
|
}, [stdout]);
|
|
@@ -1558,10 +1916,15 @@ var App = ({
|
|
|
1558
1916
|
}
|
|
1559
1917
|
const toggleCurrent = () => {
|
|
1560
1918
|
if (sortMenuIndex === 0) {
|
|
1561
|
-
setSortPreferences((prev) =>
|
|
1919
|
+
setSortPreferences((prev) => {
|
|
1920
|
+
const nextPrimary = prev.primary === "updated" ? "name" : "updated";
|
|
1921
|
+
const nextDirection = nextPrimary === "name" && !directionManuallyChanged ? "asc" : prev.direction;
|
|
1922
|
+
return { ...prev, primary: nextPrimary, direction: nextDirection };
|
|
1923
|
+
});
|
|
1562
1924
|
return;
|
|
1563
1925
|
}
|
|
1564
1926
|
if (sortMenuIndex === 1) {
|
|
1927
|
+
setDirectionManuallyChanged(true);
|
|
1565
1928
|
setSortPreferences((prev) => ({ ...prev, direction: prev.direction === "asc" ? "desc" : "asc" }));
|
|
1566
1929
|
return;
|
|
1567
1930
|
}
|
|
@@ -1710,7 +2073,7 @@ var App = ({
|
|
|
1710
2073
|
) })
|
|
1711
2074
|
] })
|
|
1712
2075
|
] }),
|
|
1713
|
-
statusBar: isAnyMenuOpen ? /* @__PURE__ */ jsx6(Text4, { wrap: "truncate", children: "Select: j/k, Toggle: Space, Back: Esc" }) : /* @__PURE__ */ jsx6(Text4, { wrap: "
|
|
2076
|
+
statusBar: isAnyMenuOpen ? /* @__PURE__ */ jsx6(Text4, { wrap: "truncate", children: "Select: j/k, Toggle: Space, Back: Esc" }) : /* @__PURE__ */ jsx6(Text4, { wrap: "wrap", children: hint })
|
|
1714
2077
|
}
|
|
1715
2078
|
);
|
|
1716
2079
|
};
|
|
@@ -1718,10 +2081,11 @@ var App = ({
|
|
|
1718
2081
|
// src/index.tsx
|
|
1719
2082
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1720
2083
|
var bootstrap = async () => {
|
|
1721
|
-
const
|
|
2084
|
+
const isWindows = process2.platform === "win32";
|
|
2085
|
+
const unityHubReader = isWindows ? new WinUnityHubProjectsReader() : new MacUnityHubProjectsReader();
|
|
1722
2086
|
const gitRepositoryInfoReader = new GitRepositoryInfoReader();
|
|
1723
2087
|
const lockStatusReader = new UnityLockStatusReader();
|
|
1724
|
-
const unityProcessReader = new MacUnityProcessReader();
|
|
2088
|
+
const unityProcessReader = isWindows ? new WinUnityProcessReader() : new MacUnityProcessReader();
|
|
1725
2089
|
const unityTempDirectoryCleaner = new UnityTempDirectoryCleaner();
|
|
1726
2090
|
const listProjectsUseCase = new ListProjectsUseCase(
|
|
1727
2091
|
unityHubReader,
|
|
@@ -1730,10 +2094,10 @@ var bootstrap = async () => {
|
|
|
1730
2094
|
lockStatusReader,
|
|
1731
2095
|
unityProcessReader
|
|
1732
2096
|
);
|
|
1733
|
-
const editorPathResolver = new MacEditorPathResolver();
|
|
2097
|
+
const editorPathResolver = isWindows ? new WinEditorPathResolver() : new MacEditorPathResolver();
|
|
1734
2098
|
const processLauncher = new NodeProcessLauncher();
|
|
1735
2099
|
const lockChecker = new UnityLockChecker(unityProcessReader, unityTempDirectoryCleaner);
|
|
1736
|
-
const unityProcessTerminator = new MacUnityProcessTerminator();
|
|
2100
|
+
const unityProcessTerminator = isWindows ? new WinUnityProcessTerminator() : new MacUnityProcessTerminator();
|
|
1737
2101
|
const launchProjectUseCase = new LaunchProjectUseCase(
|
|
1738
2102
|
editorPathResolver,
|
|
1739
2103
|
processLauncher,
|
|
@@ -1748,6 +2112,23 @@ var bootstrap = async () => {
|
|
|
1748
2112
|
);
|
|
1749
2113
|
const useGitRootName = !process2.argv.includes("--no-git-root-name");
|
|
1750
2114
|
try {
|
|
2115
|
+
const rawModeSupported = Boolean(
|
|
2116
|
+
process2.stdin.isTTY && typeof process2.stdin.setRawMode === "function"
|
|
2117
|
+
);
|
|
2118
|
+
if (!rawModeSupported) {
|
|
2119
|
+
const message = [
|
|
2120
|
+
"\u3053\u306E\u7AEF\u672B\u3067\u306F\u5BFE\u8A71\u5165\u529B\uFF08Raw mode\uFF09\u304C\u4F7F\u3048\u307E\u305B\u3093\u3002",
|
|
2121
|
+
"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",
|
|
2122
|
+
"MinTTY \u306E Git Bash \u3067\u306F\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044:",
|
|
2123
|
+
" - winpty cmd.exe /c npx unity-hub-cli",
|
|
2124
|
+
" - winpty powershell.exe -NoProfile -Command npx unity-hub-cli",
|
|
2125
|
+
"\uFF08\u30D3\u30EB\u30C9\u6E08\u307F\u306E\u5834\u5408\uFF09npm run build && winpty node dist/index.js",
|
|
2126
|
+
"\u8A73\u3057\u304F: https://github.com/vadimdemedes/ink/#israwmodesupported"
|
|
2127
|
+
].join("\n");
|
|
2128
|
+
console.error(message);
|
|
2129
|
+
process2.exitCode = 1;
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
1751
2132
|
const projects = await listProjectsUseCase.execute();
|
|
1752
2133
|
const { waitUntilExit } = render(
|
|
1753
2134
|
/* @__PURE__ */ jsx7(
|
|
@@ -1769,4 +2150,8 @@ var bootstrap = async () => {
|
|
|
1769
2150
|
process2.exitCode = 1;
|
|
1770
2151
|
}
|
|
1771
2152
|
};
|
|
1772
|
-
|
|
2153
|
+
void bootstrap().catch((error) => {
|
|
2154
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2155
|
+
console.error(message);
|
|
2156
|
+
process2.exitCode = 1;
|
|
2157
|
+
});
|