unity-hub-cli 0.14.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 +509 -100
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import
|
|
4
|
+
import process3 from "process";
|
|
5
5
|
import { render } from "ink";
|
|
6
6
|
|
|
7
7
|
// src/application/usecases.ts
|
|
@@ -105,6 +105,108 @@ var TerminateProjectUseCase = class {
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
|
+
var LaunchWithEditorUseCase = class {
|
|
109
|
+
constructor(launchProjectUseCase, externalEditorPathReader, externalEditorLauncher) {
|
|
110
|
+
this.launchProjectUseCase = launchProjectUseCase;
|
|
111
|
+
this.externalEditorPathReader = externalEditorPathReader;
|
|
112
|
+
this.externalEditorLauncher = externalEditorLauncher;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Launches the Unity project and attempts to open the external editor.
|
|
116
|
+
* @param project - The Unity project to launch.
|
|
117
|
+
* @returns The result of the launch operation.
|
|
118
|
+
*/
|
|
119
|
+
async execute(project) {
|
|
120
|
+
const editorResult = await this.externalEditorPathReader.read();
|
|
121
|
+
let editorLaunched = false;
|
|
122
|
+
let editorMessage = "";
|
|
123
|
+
if (editorResult.status === "found") {
|
|
124
|
+
editorLaunched = await this.tryLaunchEditor(editorResult, project.path);
|
|
125
|
+
editorMessage = editorLaunched ? ` + ${editorResult.name}` : " (Editor launch failed)";
|
|
126
|
+
} else {
|
|
127
|
+
editorMessage = this.buildEditorStatusMessage(editorResult);
|
|
128
|
+
}
|
|
129
|
+
await this.launchProjectUseCase.execute(project);
|
|
130
|
+
return {
|
|
131
|
+
unityLaunched: true,
|
|
132
|
+
editorLaunched,
|
|
133
|
+
message: `Launched: ${project.title}${editorMessage}`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Attempts to launch the external editor.
|
|
138
|
+
* @param editorResult - The found external editor result.
|
|
139
|
+
* @param projectPath - The path to the project root.
|
|
140
|
+
* @returns Whether the editor was successfully launched.
|
|
141
|
+
*/
|
|
142
|
+
async tryLaunchEditor(editorResult, projectPath) {
|
|
143
|
+
try {
|
|
144
|
+
await this.externalEditorLauncher.launch(editorResult.path, projectPath);
|
|
145
|
+
return true;
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Builds a status message for non-found editor results.
|
|
152
|
+
* @param editorResult - The external editor result.
|
|
153
|
+
* @returns The status message.
|
|
154
|
+
*/
|
|
155
|
+
buildEditorStatusMessage(editorResult) {
|
|
156
|
+
if (editorResult.status === "not_configured") {
|
|
157
|
+
return " (Editor not configured)";
|
|
158
|
+
}
|
|
159
|
+
if (editorResult.status === "not_found") {
|
|
160
|
+
return ` (Editor not found: ${editorResult.configuredPath})`;
|
|
161
|
+
}
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var LaunchEditorOnlyUseCase = class {
|
|
166
|
+
constructor(externalEditorPathReader, externalEditorLauncher) {
|
|
167
|
+
this.externalEditorPathReader = externalEditorPathReader;
|
|
168
|
+
this.externalEditorLauncher = externalEditorLauncher;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Launches only the external editor for the specified project.
|
|
172
|
+
* @param project - The Unity project to open in the editor.
|
|
173
|
+
* @returns The result of the launch operation.
|
|
174
|
+
*/
|
|
175
|
+
async execute(project) {
|
|
176
|
+
const editorResult = await this.externalEditorPathReader.read();
|
|
177
|
+
if (editorResult.status === "not_configured") {
|
|
178
|
+
return {
|
|
179
|
+
editorLaunched: false,
|
|
180
|
+
message: "Editor not configured in Unity preferences"
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (editorResult.status === "not_found") {
|
|
184
|
+
return {
|
|
185
|
+
editorLaunched: false,
|
|
186
|
+
message: `Editor not found: ${editorResult.configuredPath}`
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const launched = await this.tryLaunchEditor(editorResult, project.path);
|
|
190
|
+
return {
|
|
191
|
+
editorLaunched: launched,
|
|
192
|
+
message: launched ? `Launched: ${editorResult.name}` : `Failed to launch ${editorResult.name}`
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Attempts to launch the external editor.
|
|
197
|
+
* @param editorResult - The found external editor result.
|
|
198
|
+
* @param projectPath - The path to the project root.
|
|
199
|
+
* @returns Whether the editor was successfully launched.
|
|
200
|
+
*/
|
|
201
|
+
async tryLaunchEditor(editorResult, projectPath) {
|
|
202
|
+
try {
|
|
203
|
+
await this.externalEditorLauncher.launch(editorResult.path, projectPath);
|
|
204
|
+
return true;
|
|
205
|
+
} catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
108
210
|
|
|
109
211
|
// src/infrastructure/editor.ts
|
|
110
212
|
import { constants } from "fs";
|
|
@@ -158,9 +260,222 @@ var WinEditorPathResolver = class {
|
|
|
158
260
|
}
|
|
159
261
|
};
|
|
160
262
|
|
|
263
|
+
// src/infrastructure/externalEditor.ts
|
|
264
|
+
import { execFile } from "child_process";
|
|
265
|
+
import { constants as constants3, existsSync } from "fs";
|
|
266
|
+
import { access as access3 } from "fs/promises";
|
|
267
|
+
import { basename, join as join3 } from "path";
|
|
268
|
+
import { promisify } from "util";
|
|
269
|
+
var execFileAsync = promisify(execFile);
|
|
270
|
+
var PLIST_DOMAIN = "com.unity3d.UnityEditor5.x";
|
|
271
|
+
var PLIST_KEY = "kScriptsDefaultApp";
|
|
272
|
+
var MacExternalEditorPathReader = class {
|
|
273
|
+
/**
|
|
274
|
+
* Reads the external editor configuration from Unity preferences.
|
|
275
|
+
* @returns The external editor result with status and path information.
|
|
276
|
+
*/
|
|
277
|
+
async read() {
|
|
278
|
+
let configuredPath;
|
|
279
|
+
try {
|
|
280
|
+
const result = await execFileAsync("defaults", ["read", PLIST_DOMAIN, PLIST_KEY]);
|
|
281
|
+
configuredPath = result.stdout.trim();
|
|
282
|
+
} catch {
|
|
283
|
+
return { status: "not_configured" };
|
|
284
|
+
}
|
|
285
|
+
if (!configuredPath) {
|
|
286
|
+
return { status: "not_configured" };
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
await access3(configuredPath, constants3.F_OK);
|
|
290
|
+
} catch {
|
|
291
|
+
return { status: "not_found", configuredPath };
|
|
292
|
+
}
|
|
293
|
+
const name = basename(configuredPath, ".app");
|
|
294
|
+
return { status: "found", path: configuredPath, name };
|
|
295
|
+
}
|
|
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
|
+
};
|
|
302
|
+
var MacExternalEditorLauncher = class {
|
|
303
|
+
/**
|
|
304
|
+
* Launches the external editor with the specified project root.
|
|
305
|
+
* For Rider/Visual Studio: opens .sln file directly if it exists.
|
|
306
|
+
* For VS Code/Cursor and others: opens the project folder.
|
|
307
|
+
* @param editorPath - The path to the editor application.
|
|
308
|
+
* @param projectRoot - The project root directory to open.
|
|
309
|
+
*/
|
|
310
|
+
async launch(editorPath, 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
|
+
}
|
|
319
|
+
await execFileAsync("open", ["-a", editorPath, targetPath]);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// src/infrastructure/externalEditor.win.ts
|
|
324
|
+
import { execFile as execFile2, spawn } from "child_process";
|
|
325
|
+
import { constants as constants4, existsSync as existsSync2 } from "fs";
|
|
326
|
+
import { access as access4 } from "fs/promises";
|
|
327
|
+
import { basename as basename2, join as join4 } from "path";
|
|
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
|
|
378
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
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
|
+
};
|
|
390
|
+
var parseRegistryOutput = (stdout) => {
|
|
391
|
+
const lines = stdout.split("\n");
|
|
392
|
+
for (const line of lines) {
|
|
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]);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return void 0;
|
|
403
|
+
};
|
|
404
|
+
var WinExternalEditorPathReader = class {
|
|
405
|
+
/**
|
|
406
|
+
* Reads the external editor configuration from Unity preferences.
|
|
407
|
+
* @returns The external editor result with status and path information.
|
|
408
|
+
*/
|
|
409
|
+
async read() {
|
|
410
|
+
let configuredPath;
|
|
411
|
+
try {
|
|
412
|
+
const result = await execFileAsync2(
|
|
413
|
+
"reg",
|
|
414
|
+
["query", REGISTRY_PATH],
|
|
415
|
+
{ env: getMsysDisabledEnv() }
|
|
416
|
+
);
|
|
417
|
+
configuredPath = parseRegistryOutput(result.stdout);
|
|
418
|
+
} catch {
|
|
419
|
+
return { status: "not_configured" };
|
|
420
|
+
}
|
|
421
|
+
if (!configuredPath) {
|
|
422
|
+
return { status: "not_configured" };
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
await access4(configuredPath, constants4.F_OK);
|
|
426
|
+
} catch {
|
|
427
|
+
return { status: "not_found", configuredPath };
|
|
428
|
+
}
|
|
429
|
+
const name = basename2(configuredPath, ".exe");
|
|
430
|
+
return { status: "found", path: configuredPath, name };
|
|
431
|
+
}
|
|
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
|
+
};
|
|
438
|
+
var WinExternalEditorLauncher = class {
|
|
439
|
+
/**
|
|
440
|
+
* Launches the external editor with the specified project root.
|
|
441
|
+
* For Rider/Visual Studio: opens .sln file directly if it exists.
|
|
442
|
+
* For VS Code/Cursor and others: opens the project folder.
|
|
443
|
+
* @param editorPath - The path to the editor executable.
|
|
444
|
+
* @param projectRoot - The project root directory to open.
|
|
445
|
+
*/
|
|
446
|
+
async launch(editorPath, 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
|
+
}
|
|
455
|
+
await new Promise((resolve4, reject) => {
|
|
456
|
+
const child = spawn(editorPath, [targetPath], {
|
|
457
|
+
detached: true,
|
|
458
|
+
stdio: "ignore",
|
|
459
|
+
env: getMsysDisabledEnv()
|
|
460
|
+
});
|
|
461
|
+
const handleError = (error) => {
|
|
462
|
+
child.off("spawn", handleSpawn);
|
|
463
|
+
reject(error);
|
|
464
|
+
};
|
|
465
|
+
const handleSpawn = () => {
|
|
466
|
+
child.off("error", handleError);
|
|
467
|
+
child.unref();
|
|
468
|
+
resolve4();
|
|
469
|
+
};
|
|
470
|
+
child.once("error", handleError);
|
|
471
|
+
child.once("spawn", handleSpawn);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
161
476
|
// src/infrastructure/git.ts
|
|
162
477
|
import { readFile, stat } from "fs/promises";
|
|
163
|
-
import { dirname, join as
|
|
478
|
+
import { dirname, join as join5, resolve } from "path";
|
|
164
479
|
var HEAD_FILE = "HEAD";
|
|
165
480
|
var GIT_DIR = ".git";
|
|
166
481
|
var MAX_ASCENT = 50;
|
|
@@ -183,7 +498,7 @@ var isFile = async (path) => {
|
|
|
183
498
|
var findGitDir = async (start) => {
|
|
184
499
|
let current = resolve(start);
|
|
185
500
|
for (let depth = 0; depth < MAX_ASCENT; depth += 1) {
|
|
186
|
-
const candidate =
|
|
501
|
+
const candidate = join5(current, GIT_DIR);
|
|
187
502
|
if (await isDirectory(candidate)) {
|
|
188
503
|
return candidate;
|
|
189
504
|
}
|
|
@@ -227,7 +542,7 @@ var GitRepositoryInfoReader = class {
|
|
|
227
542
|
return void 0;
|
|
228
543
|
}
|
|
229
544
|
try {
|
|
230
|
-
const headPath =
|
|
545
|
+
const headPath = join5(gitDir, HEAD_FILE);
|
|
231
546
|
const content = await readFile(headPath, "utf8");
|
|
232
547
|
const branch = parseHead(content);
|
|
233
548
|
const root = dirname(gitDir);
|
|
@@ -239,14 +554,15 @@ var GitRepositoryInfoReader = class {
|
|
|
239
554
|
};
|
|
240
555
|
|
|
241
556
|
// src/infrastructure/process.ts
|
|
242
|
-
import { spawn } from "child_process";
|
|
557
|
+
import { spawn as spawn2 } from "child_process";
|
|
243
558
|
var NodeProcessLauncher = class {
|
|
244
559
|
async launch(command, args, options) {
|
|
245
560
|
const detached = options?.detached ?? false;
|
|
246
561
|
await new Promise((resolve4, reject) => {
|
|
247
|
-
const child =
|
|
562
|
+
const child = spawn2(command, args, {
|
|
248
563
|
detached,
|
|
249
|
-
stdio: "ignore"
|
|
564
|
+
stdio: "ignore",
|
|
565
|
+
env: getMsysDisabledEnv()
|
|
250
566
|
});
|
|
251
567
|
const handleError = (error) => {
|
|
252
568
|
child.off("spawn", handleSpawn);
|
|
@@ -360,7 +676,7 @@ var detectTerminalTheme = async (timeoutMs = 100) => {
|
|
|
360
676
|
|
|
361
677
|
// src/infrastructure/unityhub.ts
|
|
362
678
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
363
|
-
import { basename } from "path";
|
|
679
|
+
import { basename as basename3 } from "path";
|
|
364
680
|
var HUB_PROJECTS_PATH = `${process.env.HOME ?? ""}/Library/Application Support/UnityHub/projects-v1.json`;
|
|
365
681
|
var schemaVersion = "v1";
|
|
366
682
|
var toUnityProject = (entry) => {
|
|
@@ -375,7 +691,7 @@ var toUnityProject = (entry) => {
|
|
|
375
691
|
const lastModified = typeof entry.lastModified === "number" ? new Date(entry.lastModified) : void 0;
|
|
376
692
|
return {
|
|
377
693
|
id: safePath,
|
|
378
|
-
title: entry.title?.trim() ||
|
|
694
|
+
title: entry.title?.trim() || basename3(safePath),
|
|
379
695
|
path: safePath,
|
|
380
696
|
version: { value: version },
|
|
381
697
|
lastModified,
|
|
@@ -490,9 +806,9 @@ var MacUnityHubProjectsReader = class {
|
|
|
490
806
|
|
|
491
807
|
// src/infrastructure/unityhub.win.ts
|
|
492
808
|
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
493
|
-
import { basename as
|
|
494
|
-
var HUB_DIR =
|
|
495
|
-
var HUB_PROJECTS_PATH2 =
|
|
809
|
+
import { basename as basename4, join as join6 } from "path";
|
|
810
|
+
var HUB_DIR = join6(process.env.APPDATA ?? "", "UnityHub");
|
|
811
|
+
var HUB_PROJECTS_PATH2 = join6(HUB_DIR, "projects-v1.json");
|
|
496
812
|
var schemaVersion2 = "v1";
|
|
497
813
|
var toUnityProject2 = (entry) => {
|
|
498
814
|
const safePath = entry.path;
|
|
@@ -506,7 +822,7 @@ var toUnityProject2 = (entry) => {
|
|
|
506
822
|
const lastModified = typeof entry.lastModified === "number" ? new Date(entry.lastModified) : void 0;
|
|
507
823
|
return {
|
|
508
824
|
id: safePath,
|
|
509
|
-
title: entry.title?.trim() ||
|
|
825
|
+
title: entry.title?.trim() || basename4(safePath),
|
|
510
826
|
path: safePath,
|
|
511
827
|
version: { value: version },
|
|
512
828
|
lastModified,
|
|
@@ -594,7 +910,7 @@ var WinUnityHubProjectsReader = class {
|
|
|
594
910
|
await writeFile2(HUB_PROJECTS_PATH2, JSON.stringify(json, void 0, 2), "utf8");
|
|
595
911
|
}
|
|
596
912
|
async readCliArgs(projectPath) {
|
|
597
|
-
const infoPath =
|
|
913
|
+
const infoPath = join6(HUB_DIR, "projectsInfo.json");
|
|
598
914
|
let content;
|
|
599
915
|
try {
|
|
600
916
|
content = await readFile3(infoPath, "utf8");
|
|
@@ -620,18 +936,18 @@ var WinUnityHubProjectsReader = class {
|
|
|
620
936
|
};
|
|
621
937
|
|
|
622
938
|
// src/infrastructure/unityLock.ts
|
|
623
|
-
import { execFile } from "child_process";
|
|
624
|
-
import { constants as
|
|
625
|
-
import { access as
|
|
626
|
-
import { join as
|
|
627
|
-
import { promisify } from "util";
|
|
628
|
-
var
|
|
939
|
+
import { execFile as execFile3 } from "child_process";
|
|
940
|
+
import { constants as constants5 } from "fs";
|
|
941
|
+
import { access as access5, rm } from "fs/promises";
|
|
942
|
+
import { join as join7 } from "path";
|
|
943
|
+
import { promisify as promisify3 } from "util";
|
|
944
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
629
945
|
var buildBringToFrontScript = (pid) => {
|
|
630
946
|
return `tell application "System Events" to set frontmost of (first process whose unix id is ${pid}) to true`;
|
|
631
947
|
};
|
|
632
948
|
var pathExists = async (target) => {
|
|
633
949
|
try {
|
|
634
|
-
await
|
|
950
|
+
await access5(target, constants5.F_OK);
|
|
635
951
|
return true;
|
|
636
952
|
} catch {
|
|
637
953
|
return false;
|
|
@@ -651,7 +967,7 @@ var UnityLockChecker = class {
|
|
|
651
967
|
await this.bringUnityToFront(activeProcess.pid);
|
|
652
968
|
return "skip";
|
|
653
969
|
}
|
|
654
|
-
const lockfilePath =
|
|
970
|
+
const lockfilePath = join7(projectPath, "Temp", "UnityLockfile");
|
|
655
971
|
const hasLockfile = await pathExists(lockfilePath);
|
|
656
972
|
if (!hasLockfile) {
|
|
657
973
|
return "allow";
|
|
@@ -680,7 +996,7 @@ var UnityLockChecker = class {
|
|
|
680
996
|
}
|
|
681
997
|
try {
|
|
682
998
|
const script = buildBringToFrontScript(pid);
|
|
683
|
-
await
|
|
999
|
+
await execFileAsync3("osascript", ["-e", script]);
|
|
684
1000
|
} catch (error) {
|
|
685
1001
|
const message = error instanceof Error ? error.message : String(error);
|
|
686
1002
|
console.error(`Failed to bring Unity to front: ${message}`);
|
|
@@ -689,16 +1005,16 @@ var UnityLockChecker = class {
|
|
|
689
1005
|
};
|
|
690
1006
|
var UnityLockStatusReader = class {
|
|
691
1007
|
async isLocked(projectPath) {
|
|
692
|
-
const lockfilePath =
|
|
1008
|
+
const lockfilePath = join7(projectPath, "Temp", "UnityLockfile");
|
|
693
1009
|
return await pathExists(lockfilePath);
|
|
694
1010
|
}
|
|
695
1011
|
};
|
|
696
1012
|
|
|
697
1013
|
// src/infrastructure/unityProcess.ts
|
|
698
|
-
import { execFile as
|
|
1014
|
+
import { execFile as execFile4 } from "child_process";
|
|
699
1015
|
import { resolve as resolve2 } from "path";
|
|
700
|
-
import { promisify as
|
|
701
|
-
var
|
|
1016
|
+
import { promisify as promisify4 } from "util";
|
|
1017
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
702
1018
|
var UNITY_EXECUTABLE_PATTERN = /Unity\.app\/Contents\/MacOS\/Unity/i;
|
|
703
1019
|
var PROJECT_PATH_PATTERN = /-(?:projectPath|projectpath)(?:=|\s+)("[^"]+"|'[^']+'|[^\s"']+)/i;
|
|
704
1020
|
var PROCESS_LIST_ARGS = ["-axo", "pid=,command=", "-ww"];
|
|
@@ -781,7 +1097,7 @@ var MacUnityProcessReader = class {
|
|
|
781
1097
|
async listUnityProcesses() {
|
|
782
1098
|
let stdout;
|
|
783
1099
|
try {
|
|
784
|
-
const result = await
|
|
1100
|
+
const result = await execFileAsync4(PROCESS_LIST_COMMAND, PROCESS_LIST_ARGS);
|
|
785
1101
|
stdout = result.stdout;
|
|
786
1102
|
} catch (error) {
|
|
787
1103
|
throw new Error(`Failed to retrieve Unity process list: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -811,7 +1127,7 @@ var MacUnityProcessReader = class {
|
|
|
811
1127
|
pid: pidValue,
|
|
812
1128
|
projectPath: normalizePath(projectArgument)
|
|
813
1129
|
};
|
|
814
|
-
}).filter((
|
|
1130
|
+
}).filter((process4) => Boolean(process4));
|
|
815
1131
|
}
|
|
816
1132
|
};
|
|
817
1133
|
var MacUnityProcessTerminator = class {
|
|
@@ -826,7 +1142,7 @@ var MacUnityProcessTerminator = class {
|
|
|
826
1142
|
' keystroke "q" using {command down}',
|
|
827
1143
|
"end tell"
|
|
828
1144
|
].join("\n");
|
|
829
|
-
await
|
|
1145
|
+
await execFileAsync4("osascript", ["-e", script]);
|
|
830
1146
|
const deadlineGraceful = Date.now() + GRACEFUL_QUIT_TIMEOUT_MILLIS;
|
|
831
1147
|
while (Date.now() < deadlineGraceful) {
|
|
832
1148
|
await delay(GRACEFUL_QUIT_POLL_INTERVAL_MILLIS);
|
|
@@ -873,10 +1189,10 @@ var MacUnityProcessTerminator = class {
|
|
|
873
1189
|
};
|
|
874
1190
|
|
|
875
1191
|
// src/infrastructure/unityProcess.win.ts
|
|
876
|
-
import { execFile as
|
|
1192
|
+
import { execFile as execFile5 } from "child_process";
|
|
877
1193
|
import { resolve as resolve3 } from "path";
|
|
878
|
-
import { promisify as
|
|
879
|
-
var
|
|
1194
|
+
import { promisify as promisify5 } from "util";
|
|
1195
|
+
var execFileAsync5 = promisify5(execFile5);
|
|
880
1196
|
var PROJECT_PATH_PATTERN2 = /-(?:projectPath|projectpath)(?:=|\s+)("[^"]+"|'[^']+'|[^\s"']+)/i;
|
|
881
1197
|
var TERMINATE_TIMEOUT_MILLIS2 = 5e3;
|
|
882
1198
|
var TERMINATE_POLL_INTERVAL_MILLIS2 = 200;
|
|
@@ -967,7 +1283,7 @@ var WinUnityProcessReader = class {
|
|
|
967
1283
|
].join(" ");
|
|
968
1284
|
let stdout;
|
|
969
1285
|
try {
|
|
970
|
-
const result = await
|
|
1286
|
+
const result = await execFileAsync5(
|
|
971
1287
|
"powershell.exe",
|
|
972
1288
|
[
|
|
973
1289
|
"-NoProfile",
|
|
@@ -977,7 +1293,7 @@ var WinUnityProcessReader = class {
|
|
|
977
1293
|
"-Command",
|
|
978
1294
|
psCommand
|
|
979
1295
|
],
|
|
980
|
-
{ encoding: "utf8" }
|
|
1296
|
+
{ encoding: "utf8", env: getMsysDisabledEnv() }
|
|
981
1297
|
);
|
|
982
1298
|
stdout = (result.stdout ?? "").trim();
|
|
983
1299
|
} catch (error) {
|
|
@@ -1004,14 +1320,18 @@ var WinUnityProcessReader = class {
|
|
|
1004
1320
|
var WinUnityProcessTerminator = class {
|
|
1005
1321
|
async terminate(unityProcess) {
|
|
1006
1322
|
try {
|
|
1007
|
-
await
|
|
1008
|
-
"
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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
|
+
);
|
|
1015
1335
|
} catch (error) {
|
|
1016
1336
|
if (!ensureProcessAlive2(unityProcess.pid)) {
|
|
1017
1337
|
return { terminated: true, stage: "sigterm" };
|
|
@@ -1026,14 +1346,18 @@ var WinUnityProcessTerminator = class {
|
|
|
1026
1346
|
}
|
|
1027
1347
|
}
|
|
1028
1348
|
try {
|
|
1029
|
-
await
|
|
1030
|
-
"
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
+
);
|
|
1037
1361
|
} catch (error) {
|
|
1038
1362
|
if (!ensureProcessAlive2(unityProcess.pid)) {
|
|
1039
1363
|
return { terminated: true, stage: "sigkill" };
|
|
@@ -1048,11 +1372,11 @@ var WinUnityProcessTerminator = class {
|
|
|
1048
1372
|
|
|
1049
1373
|
// src/infrastructure/unityTemp.ts
|
|
1050
1374
|
import { rm as rm2 } from "fs/promises";
|
|
1051
|
-
import { join as
|
|
1375
|
+
import { join as join8 } from "path";
|
|
1052
1376
|
var TEMP_DIRECTORY_NAME = "Temp";
|
|
1053
1377
|
var UnityTempDirectoryCleaner = class {
|
|
1054
1378
|
async clean(projectPath) {
|
|
1055
|
-
const tempDirectoryPath =
|
|
1379
|
+
const tempDirectoryPath = join8(projectPath, TEMP_DIRECTORY_NAME);
|
|
1056
1380
|
try {
|
|
1057
1381
|
await rm2(tempDirectoryPath, {
|
|
1058
1382
|
recursive: true,
|
|
@@ -1064,7 +1388,8 @@ var UnityTempDirectoryCleaner = class {
|
|
|
1064
1388
|
};
|
|
1065
1389
|
|
|
1066
1390
|
// src/presentation/App.tsx
|
|
1067
|
-
import { basename as
|
|
1391
|
+
import { basename as basename6 } from "path";
|
|
1392
|
+
import process2 from "process";
|
|
1068
1393
|
import clipboard from "clipboardy";
|
|
1069
1394
|
import { Box as Box6, Text as Text4, useApp, useInput, useStdout as useStdout2 } from "ink";
|
|
1070
1395
|
import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useState as useState4 } from "react";
|
|
@@ -1108,7 +1433,7 @@ var LayoutManager = ({
|
|
|
1108
1433
|
};
|
|
1109
1434
|
|
|
1110
1435
|
// src/presentation/components/ProjectList.tsx
|
|
1111
|
-
import { basename as
|
|
1436
|
+
import { basename as basename5 } from "path";
|
|
1112
1437
|
import { Box as Box3 } from "ink";
|
|
1113
1438
|
import { useMemo } from "react";
|
|
1114
1439
|
|
|
@@ -1162,39 +1487,6 @@ var ThemeProvider = ({ theme, children }) => {
|
|
|
1162
1487
|
return createElement(ThemeContext.Provider, { value }, children);
|
|
1163
1488
|
};
|
|
1164
1489
|
|
|
1165
|
-
// src/presentation/utils/path.ts
|
|
1166
|
-
var homeDirectory = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
1167
|
-
var normalizedHomeDirectory = homeDirectory.replace(/\\/g, "/");
|
|
1168
|
-
var homePrefix = normalizedHomeDirectory ? `${normalizedHomeDirectory}/` : "";
|
|
1169
|
-
var shortenHomePath = (targetPath) => {
|
|
1170
|
-
if (!normalizedHomeDirectory) {
|
|
1171
|
-
return targetPath;
|
|
1172
|
-
}
|
|
1173
|
-
const normalizedTarget = targetPath.replace(/\\/g, "/");
|
|
1174
|
-
if (normalizedTarget === normalizedHomeDirectory) {
|
|
1175
|
-
return "~";
|
|
1176
|
-
}
|
|
1177
|
-
if (homePrefix && normalizedTarget.startsWith(homePrefix)) {
|
|
1178
|
-
return `~/${normalizedTarget.slice(homePrefix.length)}`;
|
|
1179
|
-
}
|
|
1180
|
-
return targetPath;
|
|
1181
|
-
};
|
|
1182
|
-
var buildCdCommand = (targetPath) => {
|
|
1183
|
-
if (process.platform === "win32") {
|
|
1184
|
-
const isGitBash = Boolean(process.env.MSYSTEM) || /bash/i.test(process.env.SHELL ?? "");
|
|
1185
|
-
if (isGitBash) {
|
|
1186
|
-
const windowsPath = targetPath;
|
|
1187
|
-
const msysPath = windowsPath.replace(/^([A-Za-z]):[\\/]/, (_, drive) => `/${drive.toLowerCase()}/`).replace(/\\/g, "/");
|
|
1188
|
-
const escapedForPosix2 = msysPath.replace(/'/g, "'\\''");
|
|
1189
|
-
return `cd '${escapedForPosix2}'`;
|
|
1190
|
-
}
|
|
1191
|
-
const escapedForWindows = targetPath.replace(/"/g, '""');
|
|
1192
|
-
return `cd "${escapedForWindows}"`;
|
|
1193
|
-
}
|
|
1194
|
-
const escapedForPosix = targetPath.replace(/'/g, "'\\''");
|
|
1195
|
-
return `cd '${escapedForPosix}'`;
|
|
1196
|
-
};
|
|
1197
|
-
|
|
1198
1490
|
// src/presentation/components/ProjectRow.tsx
|
|
1199
1491
|
import { Box as Box2, Text, useStdout } from "ink";
|
|
1200
1492
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -1258,7 +1550,7 @@ var extractRootFolder = (repository) => {
|
|
|
1258
1550
|
if (!repository?.root) {
|
|
1259
1551
|
return void 0;
|
|
1260
1552
|
}
|
|
1261
|
-
const base =
|
|
1553
|
+
const base = basename5(repository.root);
|
|
1262
1554
|
return base || void 0;
|
|
1263
1555
|
};
|
|
1264
1556
|
var formatProjectName = (projectTitle, repository, useGitRootName) => {
|
|
@@ -1691,11 +1983,11 @@ var extractRootFolder2 = (repository) => {
|
|
|
1691
1983
|
if (!repository?.root) {
|
|
1692
1984
|
return void 0;
|
|
1693
1985
|
}
|
|
1694
|
-
const base =
|
|
1986
|
+
const base = basename6(repository.root);
|
|
1695
1987
|
return base || void 0;
|
|
1696
1988
|
};
|
|
1697
1989
|
var minimumVisibleProjectCount = 4;
|
|
1698
|
-
var defaultHintMessage =
|
|
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`;
|
|
1699
1991
|
var getCopyTargetPath = (view) => {
|
|
1700
1992
|
const root = view.repository?.root;
|
|
1701
1993
|
return root && root.length > 0 ? root : view.project.path;
|
|
@@ -1703,6 +1995,8 @@ var getCopyTargetPath = (view) => {
|
|
|
1703
1995
|
var App = ({
|
|
1704
1996
|
projects,
|
|
1705
1997
|
onLaunch,
|
|
1998
|
+
onLaunchWithEditor,
|
|
1999
|
+
onLaunchEditorOnly,
|
|
1706
2000
|
onTerminate,
|
|
1707
2001
|
onRefresh,
|
|
1708
2002
|
useGitRootName = true
|
|
@@ -1799,9 +2093,9 @@ var App = ({
|
|
|
1799
2093
|
const handleSigint = () => {
|
|
1800
2094
|
exit();
|
|
1801
2095
|
};
|
|
1802
|
-
|
|
2096
|
+
process2.on("SIGINT", handleSigint);
|
|
1803
2097
|
return () => {
|
|
1804
|
-
|
|
2098
|
+
process2.off("SIGINT", handleSigint);
|
|
1805
2099
|
};
|
|
1806
2100
|
}, [exit]);
|
|
1807
2101
|
const limit = Math.max(1, visibleCount);
|
|
@@ -1948,6 +2242,100 @@ var App = ({
|
|
|
1948
2242
|
}, 3e3);
|
|
1949
2243
|
}
|
|
1950
2244
|
}, [index, onLaunch, sortedProjects]);
|
|
2245
|
+
const launchSelectedWithEditor = useCallback(async () => {
|
|
2246
|
+
if (!onLaunchWithEditor) {
|
|
2247
|
+
setHint("Launch with editor not available");
|
|
2248
|
+
setTimeout(() => {
|
|
2249
|
+
setHint(defaultHintMessage);
|
|
2250
|
+
}, 2e3);
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
const projectView = sortedProjects[index];
|
|
2254
|
+
if (!projectView) {
|
|
2255
|
+
setHint("No project to launch");
|
|
2256
|
+
setTimeout(() => {
|
|
2257
|
+
setHint(defaultHintMessage);
|
|
2258
|
+
}, 2e3);
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
const { project } = projectView;
|
|
2262
|
+
try {
|
|
2263
|
+
const cdTarget = getCopyTargetPath(projectView);
|
|
2264
|
+
const command = buildCdCommand(cdTarget);
|
|
2265
|
+
clipboard.writeSync(command);
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2268
|
+
setHint(`Failed to copy: ${message}`);
|
|
2269
|
+
setTimeout(() => {
|
|
2270
|
+
setHint(defaultHintMessage);
|
|
2271
|
+
}, 3e3);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
try {
|
|
2275
|
+
const result = await onLaunchWithEditor(project);
|
|
2276
|
+
setLaunchedProjects((previous) => {
|
|
2277
|
+
const next = new Set(previous);
|
|
2278
|
+
next.add(project.id);
|
|
2279
|
+
return next;
|
|
2280
|
+
});
|
|
2281
|
+
setReleasedProjects((previous) => {
|
|
2282
|
+
if (!previous.has(project.id)) {
|
|
2283
|
+
return previous;
|
|
2284
|
+
}
|
|
2285
|
+
const next = new Set(previous);
|
|
2286
|
+
next.delete(project.id);
|
|
2287
|
+
return next;
|
|
2288
|
+
});
|
|
2289
|
+
setHint(result.message);
|
|
2290
|
+
setTimeout(() => {
|
|
2291
|
+
setHint(defaultHintMessage);
|
|
2292
|
+
}, 3e3);
|
|
2293
|
+
} catch (error) {
|
|
2294
|
+
if (error instanceof LaunchCancelledError) {
|
|
2295
|
+
setHint("Launch cancelled");
|
|
2296
|
+
setTimeout(() => {
|
|
2297
|
+
setHint(defaultHintMessage);
|
|
2298
|
+
}, 3e3);
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2302
|
+
setHint(`Failed to launch: ${message}`);
|
|
2303
|
+
setTimeout(() => {
|
|
2304
|
+
setHint(defaultHintMessage);
|
|
2305
|
+
}, 3e3);
|
|
2306
|
+
}
|
|
2307
|
+
}, [index, onLaunchWithEditor, sortedProjects]);
|
|
2308
|
+
const launchEditorOnly = useCallback(async () => {
|
|
2309
|
+
if (!onLaunchEditorOnly) {
|
|
2310
|
+
setHint("Launch editor only not available");
|
|
2311
|
+
setTimeout(() => {
|
|
2312
|
+
setHint(defaultHintMessage);
|
|
2313
|
+
}, 2e3);
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
const projectView = sortedProjects[index];
|
|
2317
|
+
if (!projectView) {
|
|
2318
|
+
setHint("No project to open");
|
|
2319
|
+
setTimeout(() => {
|
|
2320
|
+
setHint(defaultHintMessage);
|
|
2321
|
+
}, 2e3);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
const { project } = projectView;
|
|
2325
|
+
try {
|
|
2326
|
+
const result = await onLaunchEditorOnly(project);
|
|
2327
|
+
setHint(result.message);
|
|
2328
|
+
setTimeout(() => {
|
|
2329
|
+
setHint(defaultHintMessage);
|
|
2330
|
+
}, 3e3);
|
|
2331
|
+
} catch (error) {
|
|
2332
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2333
|
+
setHint(`Failed to launch editor: ${message}`);
|
|
2334
|
+
setTimeout(() => {
|
|
2335
|
+
setHint(defaultHintMessage);
|
|
2336
|
+
}, 3e3);
|
|
2337
|
+
}
|
|
2338
|
+
}, [index, onLaunchEditorOnly, sortedProjects]);
|
|
1951
2339
|
const terminateSelected = useCallback(async () => {
|
|
1952
2340
|
const projectView = sortedProjects[index];
|
|
1953
2341
|
if (!projectView) {
|
|
@@ -2151,10 +2539,18 @@ var App = ({
|
|
|
2151
2539
|
void terminateSelected();
|
|
2152
2540
|
return;
|
|
2153
2541
|
}
|
|
2542
|
+
if (input === "i") {
|
|
2543
|
+
void launchEditorOnly();
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2154
2546
|
if (input === "o") {
|
|
2155
2547
|
void launchSelected();
|
|
2156
2548
|
return;
|
|
2157
2549
|
}
|
|
2550
|
+
if (input === "O") {
|
|
2551
|
+
void launchSelectedWithEditor();
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2158
2554
|
if (input === "r") {
|
|
2159
2555
|
void refreshProjects();
|
|
2160
2556
|
return;
|
|
@@ -2239,7 +2635,7 @@ var App = ({
|
|
|
2239
2635
|
// src/index.tsx
|
|
2240
2636
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2241
2637
|
var bootstrap = async () => {
|
|
2242
|
-
const isWindows =
|
|
2638
|
+
const isWindows = process3.platform === "win32";
|
|
2243
2639
|
const unityHubReader = isWindows ? new WinUnityHubProjectsReader() : new MacUnityHubProjectsReader();
|
|
2244
2640
|
const gitRepositoryInfoReader = new GitRepositoryInfoReader();
|
|
2245
2641
|
const lockStatusReader = new UnityLockStatusReader();
|
|
@@ -2268,10 +2664,21 @@ var bootstrap = async () => {
|
|
|
2268
2664
|
unityProcessTerminator,
|
|
2269
2665
|
unityTempDirectoryCleaner
|
|
2270
2666
|
);
|
|
2271
|
-
const
|
|
2667
|
+
const externalEditorPathReader = isWindows ? new WinExternalEditorPathReader() : new MacExternalEditorPathReader();
|
|
2668
|
+
const externalEditorLauncher = isWindows ? new WinExternalEditorLauncher() : new MacExternalEditorLauncher();
|
|
2669
|
+
const launchWithEditorUseCase = new LaunchWithEditorUseCase(
|
|
2670
|
+
launchProjectUseCase,
|
|
2671
|
+
externalEditorPathReader,
|
|
2672
|
+
externalEditorLauncher
|
|
2673
|
+
);
|
|
2674
|
+
const launchEditorOnlyUseCase = new LaunchEditorOnlyUseCase(
|
|
2675
|
+
externalEditorPathReader,
|
|
2676
|
+
externalEditorLauncher
|
|
2677
|
+
);
|
|
2678
|
+
const useGitRootName = !process3.argv.includes("--no-git-root-name");
|
|
2272
2679
|
try {
|
|
2273
2680
|
const rawModeSupported = Boolean(
|
|
2274
|
-
|
|
2681
|
+
process3.stdin.isTTY && typeof process3.stdin.setRawMode === "function"
|
|
2275
2682
|
);
|
|
2276
2683
|
if (!rawModeSupported) {
|
|
2277
2684
|
const message = [
|
|
@@ -2284,7 +2691,7 @@ var bootstrap = async () => {
|
|
|
2284
2691
|
"Details: https://github.com/vadimdemedes/ink/#israwmodesupported"
|
|
2285
2692
|
].join("\n");
|
|
2286
2693
|
console.error(message);
|
|
2287
|
-
|
|
2694
|
+
process3.exitCode = 1;
|
|
2288
2695
|
return;
|
|
2289
2696
|
}
|
|
2290
2697
|
const theme = await detectTerminalTheme();
|
|
@@ -2295,6 +2702,8 @@ var bootstrap = async () => {
|
|
|
2295
2702
|
{
|
|
2296
2703
|
projects,
|
|
2297
2704
|
onLaunch: (project) => launchProjectUseCase.execute(project),
|
|
2705
|
+
onLaunchWithEditor: (project) => launchWithEditorUseCase.execute(project),
|
|
2706
|
+
onLaunchEditorOnly: (project) => launchEditorOnlyUseCase.execute(project),
|
|
2298
2707
|
onTerminate: (project) => terminateProjectUseCase.execute(project),
|
|
2299
2708
|
onRefresh: () => listProjectsUseCase.execute(),
|
|
2300
2709
|
useGitRootName
|
|
@@ -2302,15 +2711,15 @@ var bootstrap = async () => {
|
|
|
2302
2711
|
) })
|
|
2303
2712
|
);
|
|
2304
2713
|
await waitUntilExit();
|
|
2305
|
-
|
|
2714
|
+
process3.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
2306
2715
|
} catch (error) {
|
|
2307
2716
|
const message = error instanceof Error ? error.message : String(error);
|
|
2308
2717
|
console.error(message);
|
|
2309
|
-
|
|
2718
|
+
process3.exitCode = 1;
|
|
2310
2719
|
}
|
|
2311
2720
|
};
|
|
2312
2721
|
void bootstrap().catch((error) => {
|
|
2313
2722
|
const message = error instanceof Error ? error.message : String(error);
|
|
2314
2723
|
console.error(message);
|
|
2315
|
-
|
|
2724
|
+
process3.exitCode = 1;
|
|
2316
2725
|
});
|
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
|
}
|