launch-unity 0.11.0 → 0.13.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.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/launch.d.ts +30 -0
- package/dist/launch.d.ts.map +1 -0
- package/dist/launch.js +32 -15
- package/dist/lib.d.ts +35 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +561 -0
- package/dist/unityHub.d.ts +5 -0
- package/dist/unityHub.d.ts.map +1 -0
- package/dist/unityHub.js +97 -0
- package/package.json +14 -7
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch-unity library entry point.
|
|
3
|
+
* Exports core functions for programmatic usage.
|
|
4
|
+
* Uses lib.ts which has no CLI side effects.
|
|
5
|
+
*/
|
|
6
|
+
export { LaunchOptions, LaunchResolvedOptions, UnityProcessInfo, parseArgs, findUnityProjectBfs, getUnityVersion, launch, findRunningUnityProcess, focusUnityProcess, killRunningUnity, handleStaleLockfile, ensureProjectEntryAndUpdate, updateLastModifiedIfExists, } from './lib.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAEL,aAAa,EACb,qBAAqB,EACrB,gBAAgB,EAEhB,SAAS,EACT,mBAAmB,EACnB,eAAe,EACf,MAAM,EACN,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch-unity library entry point.
|
|
3
|
+
* Exports core functions for programmatic usage.
|
|
4
|
+
* Uses lib.ts which has no CLI side effects.
|
|
5
|
+
*/
|
|
6
|
+
export {
|
|
7
|
+
// Functions
|
|
8
|
+
parseArgs, findUnityProjectBfs, getUnityVersion, launch, findRunningUnityProcess, focusUnityProcess, killRunningUnity, handleStaleLockfile, ensureProjectEntryAndUpdate, updateLastModifiedIfExists, } from './lib.js';
|
package/dist/launch.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export type LaunchOptions = {
|
|
3
|
+
subcommand?: "update";
|
|
4
|
+
projectPath?: string;
|
|
5
|
+
platform?: string | undefined;
|
|
6
|
+
unityArgs: string[];
|
|
7
|
+
searchMaxDepth: number;
|
|
8
|
+
restart: boolean;
|
|
9
|
+
addUnityHub: boolean;
|
|
10
|
+
favoriteUnityHub: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type LaunchResolvedOptions = {
|
|
13
|
+
projectPath: string;
|
|
14
|
+
platform?: string | undefined;
|
|
15
|
+
unityArgs: string[];
|
|
16
|
+
unityVersion: string;
|
|
17
|
+
};
|
|
18
|
+
export type UnityProcessInfo = {
|
|
19
|
+
pid: number;
|
|
20
|
+
projectPath: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function parseArgs(argv: string[]): LaunchOptions;
|
|
23
|
+
export declare function getUnityVersion(projectPath: string): string;
|
|
24
|
+
export declare function findRunningUnityProcess(projectPath: string): Promise<UnityProcessInfo | undefined>;
|
|
25
|
+
export declare function focusUnityProcess(pid: number): Promise<void>;
|
|
26
|
+
export declare function handleStaleLockfile(projectPath: string): Promise<void>;
|
|
27
|
+
export declare function killRunningUnity(projectPath: string): Promise<void>;
|
|
28
|
+
export declare function findUnityProjectBfs(rootDir: string, maxDepth: number): string | undefined;
|
|
29
|
+
export declare function launch(opts: LaunchResolvedOptions): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=launch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../src/launch.ts"],"names":[],"mappings":";AAeA,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAyJF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAqHvD;AAyCD,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAe3D;AA2ND,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAIxG;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlE;AA4CD,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB5E;AAiCD,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBzE;AAgDD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqDzF;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCvE"}
|
package/dist/launch.js
CHANGED
|
@@ -7,9 +7,9 @@ import { execFile, spawn } from "node:child_process";
|
|
|
7
7
|
import { existsSync, readFileSync, readdirSync, lstatSync, realpathSync } from "node:fs";
|
|
8
8
|
import { rm } from "node:fs/promises";
|
|
9
9
|
import { dirname, join, resolve } from "node:path";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
11
|
import { promisify } from "node:util";
|
|
12
|
-
import { ensureProjectEntryAndUpdate, updateLastModifiedIfExists } from "./unityHub.js";
|
|
12
|
+
import { ensureProjectEntryAndUpdate, updateLastModifiedIfExists, getProjectCliArgs } from "./unityHub.js";
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
14
|
const UNITY_EXECUTABLE_PATTERN_MAC = /Unity\.app\/Contents\/MacOS\/Unity/i;
|
|
15
15
|
const UNITY_EXECUTABLE_PATTERN_WINDOWS = /Unity\.exe/i;
|
|
@@ -130,7 +130,7 @@ const compareSemverTriplet = (left, right) => {
|
|
|
130
130
|
}
|
|
131
131
|
return 0;
|
|
132
132
|
};
|
|
133
|
-
function parseArgs(argv) {
|
|
133
|
+
export function parseArgs(argv) {
|
|
134
134
|
const args = argv.slice(2);
|
|
135
135
|
const doubleDashIndex = args.indexOf("--");
|
|
136
136
|
let cliArgs = doubleDashIndex >= 0 ? args.slice(0, doubleDashIndex) : args;
|
|
@@ -277,7 +277,7 @@ Commands:
|
|
|
277
277
|
`;
|
|
278
278
|
process.stdout.write(help);
|
|
279
279
|
}
|
|
280
|
-
function getUnityVersion(projectPath) {
|
|
280
|
+
export function getUnityVersion(projectPath) {
|
|
281
281
|
const versionFile = join(projectPath, "ProjectSettings", "ProjectVersion.txt");
|
|
282
282
|
if (!existsSync(versionFile)) {
|
|
283
283
|
console.error(`Error: ProjectVersion.txt not found at ${versionFile}`);
|
|
@@ -476,12 +476,12 @@ async function listUnityProcesses() {
|
|
|
476
476
|
}
|
|
477
477
|
return [];
|
|
478
478
|
}
|
|
479
|
-
async function findRunningUnityProcess(projectPath) {
|
|
479
|
+
export async function findRunningUnityProcess(projectPath) {
|
|
480
480
|
const normalizedTarget = normalizePath(projectPath);
|
|
481
481
|
const processes = await listUnityProcesses();
|
|
482
482
|
return processes.find((candidate) => pathsEqual(candidate.projectPath, normalizedTarget));
|
|
483
483
|
}
|
|
484
|
-
async function focusUnityProcess(pid) {
|
|
484
|
+
export async function focusUnityProcess(pid) {
|
|
485
485
|
if (process.platform === "darwin") {
|
|
486
486
|
await focusUnityProcessMac(pid);
|
|
487
487
|
return;
|
|
@@ -530,7 +530,7 @@ async function focusUnityProcessWindows(pid) {
|
|
|
530
530
|
console.warn(`Failed to bring Unity to front on Windows: ${message}`);
|
|
531
531
|
}
|
|
532
532
|
}
|
|
533
|
-
async function handleStaleLockfile(projectPath) {
|
|
533
|
+
export async function handleStaleLockfile(projectPath) {
|
|
534
534
|
const tempDirectoryPath = join(projectPath, TEMP_DIRECTORY_NAME);
|
|
535
535
|
const lockfilePath = join(tempDirectoryPath, UNITY_LOCKFILE_NAME);
|
|
536
536
|
if (!existsSync(lockfilePath)) {
|
|
@@ -584,7 +584,7 @@ async function waitForProcessExit(pid) {
|
|
|
584
584
|
}
|
|
585
585
|
return false;
|
|
586
586
|
}
|
|
587
|
-
async function killRunningUnity(projectPath) {
|
|
587
|
+
export async function killRunningUnity(projectPath) {
|
|
588
588
|
const processInfo = await findRunningUnityProcess(projectPath);
|
|
589
589
|
if (!processInfo) {
|
|
590
590
|
console.log("No running Unity process found for this project.");
|
|
@@ -643,7 +643,7 @@ function listSubdirectoriesSorted(dir) {
|
|
|
643
643
|
}
|
|
644
644
|
return entries;
|
|
645
645
|
}
|
|
646
|
-
function findUnityProjectBfs(rootDir, maxDepth) {
|
|
646
|
+
export function findUnityProjectBfs(rootDir, maxDepth) {
|
|
647
647
|
const queue = [];
|
|
648
648
|
let rootCanonical;
|
|
649
649
|
try {
|
|
@@ -695,7 +695,7 @@ function findUnityProjectBfs(rootDir, maxDepth) {
|
|
|
695
695
|
}
|
|
696
696
|
return undefined;
|
|
697
697
|
}
|
|
698
|
-
function launch(opts) {
|
|
698
|
+
export async function launch(opts) {
|
|
699
699
|
const { projectPath, platform, unityArgs, unityVersion } = opts;
|
|
700
700
|
const unityPath = getUnityPath(unityVersion);
|
|
701
701
|
console.log(`Detected Unity version: ${unityVersion}`);
|
|
@@ -711,6 +711,10 @@ function launch(opts) {
|
|
|
711
711
|
if (platform && platform.length > 0 && !unityArgsContainBuildTarget) {
|
|
712
712
|
args.push("-buildTarget", platform);
|
|
713
713
|
}
|
|
714
|
+
const hubCliArgs = await getProjectCliArgs(projectPath);
|
|
715
|
+
if (hubCliArgs.length > 0) {
|
|
716
|
+
args.push(...hubCliArgs);
|
|
717
|
+
}
|
|
714
718
|
if (unityArgs.length > 0) {
|
|
715
719
|
args.push(...unityArgs);
|
|
716
720
|
}
|
|
@@ -774,7 +778,7 @@ async function main() {
|
|
|
774
778
|
unityArgs: options.unityArgs,
|
|
775
779
|
unityVersion,
|
|
776
780
|
};
|
|
777
|
-
launch(resolved);
|
|
781
|
+
await launch(resolved);
|
|
778
782
|
// Best-effort update of Unity Hub's lastModified timestamp.
|
|
779
783
|
const now = new Date();
|
|
780
784
|
try {
|
|
@@ -838,7 +842,20 @@ async function runSelfUpdate() {
|
|
|
838
842
|
}
|
|
839
843
|
process.exit(1);
|
|
840
844
|
}
|
|
841
|
-
main()
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
+
// Only run main() when this file is executed directly (not when imported as a library)
|
|
846
|
+
let isDirectExecution = false;
|
|
847
|
+
if (typeof process.argv[1] === "string") {
|
|
848
|
+
try {
|
|
849
|
+
isDirectExecution =
|
|
850
|
+
import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
851
|
+
}
|
|
852
|
+
catch {
|
|
853
|
+
isDirectExecution = false;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (isDirectExecution) {
|
|
857
|
+
main().catch((error) => {
|
|
858
|
+
console.error(error);
|
|
859
|
+
process.exit(1);
|
|
860
|
+
});
|
|
861
|
+
}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch-unity core library functions.
|
|
3
|
+
* Pure library code without CLI entry point or side effects.
|
|
4
|
+
*/
|
|
5
|
+
import { ensureProjectEntryAndUpdate, updateLastModifiedIfExists, getProjectCliArgs, parseCliArgs } from "./unityHub.js";
|
|
6
|
+
export type LaunchOptions = {
|
|
7
|
+
subcommand?: "update";
|
|
8
|
+
projectPath?: string;
|
|
9
|
+
platform?: string | undefined;
|
|
10
|
+
unityArgs: string[];
|
|
11
|
+
searchMaxDepth: number;
|
|
12
|
+
restart: boolean;
|
|
13
|
+
addUnityHub: boolean;
|
|
14
|
+
favoriteUnityHub: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type LaunchResolvedOptions = {
|
|
17
|
+
projectPath: string;
|
|
18
|
+
platform?: string | undefined;
|
|
19
|
+
unityArgs: string[];
|
|
20
|
+
unityVersion: string;
|
|
21
|
+
};
|
|
22
|
+
export type UnityProcessInfo = {
|
|
23
|
+
pid: number;
|
|
24
|
+
projectPath: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function parseArgs(argv: string[]): LaunchOptions;
|
|
27
|
+
export declare function getUnityVersion(projectPath: string): string;
|
|
28
|
+
export declare function findRunningUnityProcess(projectPath: string): Promise<UnityProcessInfo | undefined>;
|
|
29
|
+
export declare function focusUnityProcess(pid: number): Promise<void>;
|
|
30
|
+
export declare function handleStaleLockfile(projectPath: string): Promise<void>;
|
|
31
|
+
export declare function killRunningUnity(projectPath: string): Promise<void>;
|
|
32
|
+
export declare function findUnityProjectBfs(rootDir: string, maxDepth: number): string | undefined;
|
|
33
|
+
export declare function launch(opts: LaunchResolvedOptions): Promise<void>;
|
|
34
|
+
export { ensureProjectEntryAndUpdate, updateLastModifiedIfExists, getProjectCliArgs, parseCliArgs };
|
|
35
|
+
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEzH,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAYF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAmHvD;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAc3D;AAoND,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAIxG;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlE;AA4CD,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB5E;AAiCD,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBzE;AAgDD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqDzF;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCvE;AAGD,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC"}
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launch-unity core library functions.
|
|
3
|
+
* Pure library code without CLI entry point or side effects.
|
|
4
|
+
*/
|
|
5
|
+
import { execFile, spawn } from "node:child_process";
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, lstatSync, realpathSync } from "node:fs";
|
|
7
|
+
import { rm } from "node:fs/promises";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
import { ensureProjectEntryAndUpdate, updateLastModifiedIfExists, getProjectCliArgs, parseCliArgs } from "./unityHub.js";
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
const UNITY_EXECUTABLE_PATTERN_MAC = /Unity\.app\/Contents\/MacOS\/Unity/i;
|
|
13
|
+
const UNITY_EXECUTABLE_PATTERN_WINDOWS = /Unity\.exe/i;
|
|
14
|
+
const PROJECT_PATH_PATTERN = /-(?:projectPath|projectpath)(?:=|\s+)("[^"]+"|'[^']+'|[^\s"']+)/i;
|
|
15
|
+
const PROCESS_LIST_COMMAND_MAC = "ps";
|
|
16
|
+
const PROCESS_LIST_ARGS_MAC = ["-axo", "pid=,command=", "-ww"];
|
|
17
|
+
const WINDOWS_POWERSHELL = "powershell";
|
|
18
|
+
const UNITY_LOCKFILE_NAME = "UnityLockfile";
|
|
19
|
+
const TEMP_DIRECTORY_NAME = "Temp";
|
|
20
|
+
export function parseArgs(argv) {
|
|
21
|
+
const args = argv.slice(2);
|
|
22
|
+
const doubleDashIndex = args.indexOf("--");
|
|
23
|
+
let cliArgs = doubleDashIndex >= 0 ? args.slice(0, doubleDashIndex) : args;
|
|
24
|
+
const unityArgs = doubleDashIndex >= 0 ? args.slice(doubleDashIndex + 1) : [];
|
|
25
|
+
let subcommand;
|
|
26
|
+
const firstToken = cliArgs[0];
|
|
27
|
+
if (firstToken === "update") {
|
|
28
|
+
subcommand = "update";
|
|
29
|
+
cliArgs = cliArgs.slice(1);
|
|
30
|
+
}
|
|
31
|
+
const positionals = [];
|
|
32
|
+
let maxDepth = 3; // default 3; -1 means unlimited
|
|
33
|
+
let restart = false;
|
|
34
|
+
let addUnityHub = false;
|
|
35
|
+
let favoriteUnityHub = false;
|
|
36
|
+
let platform;
|
|
37
|
+
for (let i = 0; i < cliArgs.length; i++) {
|
|
38
|
+
const arg = cliArgs[i] ?? "";
|
|
39
|
+
if (arg === "--help" || arg === "-h") {
|
|
40
|
+
continue; // Skip help flag - caller should handle
|
|
41
|
+
}
|
|
42
|
+
if (arg === "--version" || arg === "-v") {
|
|
43
|
+
continue; // Skip version flag - caller should handle
|
|
44
|
+
}
|
|
45
|
+
if (arg === "-r" || arg === "--restart") {
|
|
46
|
+
restart = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg === "-u" ||
|
|
50
|
+
arg === "-a" ||
|
|
51
|
+
arg === "--unity-hub-entry" ||
|
|
52
|
+
arg === "--add-unity-hub") {
|
|
53
|
+
addUnityHub = true;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (arg === "-f" || arg === "--favorite") {
|
|
57
|
+
favoriteUnityHub = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (arg === "-p" || arg === "--platform") {
|
|
61
|
+
const next = cliArgs[i + 1];
|
|
62
|
+
if (typeof next === "string" && !next.startsWith("-")) {
|
|
63
|
+
platform = next;
|
|
64
|
+
i += 1;
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (arg.startsWith("--platform=")) {
|
|
69
|
+
const value = arg.slice("--platform=".length);
|
|
70
|
+
if (value.length > 0) {
|
|
71
|
+
platform = value;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg.startsWith("--max-depth")) {
|
|
76
|
+
const parts = arg.split("=");
|
|
77
|
+
if (parts.length === 2) {
|
|
78
|
+
const value = Number.parseInt(parts[1] ?? "", 10);
|
|
79
|
+
if (Number.isFinite(value)) {
|
|
80
|
+
maxDepth = value;
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const next = cliArgs[i + 1];
|
|
85
|
+
if (typeof next === "string" && !next.startsWith("-")) {
|
|
86
|
+
const value = Number.parseInt(next, 10);
|
|
87
|
+
if (Number.isFinite(value)) {
|
|
88
|
+
maxDepth = value;
|
|
89
|
+
}
|
|
90
|
+
i += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg.startsWith("-")) {
|
|
96
|
+
console.warn(`Warning: Unknown option ignored: ${arg}`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
positionals.push(arg);
|
|
100
|
+
}
|
|
101
|
+
let projectPath;
|
|
102
|
+
if (positionals.length > 0) {
|
|
103
|
+
projectPath = resolve(positionals[0] ?? "");
|
|
104
|
+
}
|
|
105
|
+
if (positionals.length > 1) {
|
|
106
|
+
const ignored = positionals.slice(1).join(", ");
|
|
107
|
+
console.warn(`Warning: Extra arguments ignored: ${ignored}`);
|
|
108
|
+
console.warn(" Use -p option for platform: launch-unity -p <platform>");
|
|
109
|
+
}
|
|
110
|
+
const options = {
|
|
111
|
+
unityArgs,
|
|
112
|
+
searchMaxDepth: maxDepth,
|
|
113
|
+
restart,
|
|
114
|
+
addUnityHub,
|
|
115
|
+
favoriteUnityHub,
|
|
116
|
+
};
|
|
117
|
+
if (subcommand) {
|
|
118
|
+
options.subcommand = subcommand;
|
|
119
|
+
}
|
|
120
|
+
if (projectPath !== undefined) {
|
|
121
|
+
options.projectPath = projectPath;
|
|
122
|
+
}
|
|
123
|
+
if (platform !== undefined) {
|
|
124
|
+
options.platform = platform;
|
|
125
|
+
}
|
|
126
|
+
return options;
|
|
127
|
+
}
|
|
128
|
+
export function getUnityVersion(projectPath) {
|
|
129
|
+
const versionFile = join(projectPath, "ProjectSettings", "ProjectVersion.txt");
|
|
130
|
+
if (!existsSync(versionFile)) {
|
|
131
|
+
throw new Error(`ProjectVersion.txt not found at ${versionFile}. This does not appear to be a Unity project.`);
|
|
132
|
+
}
|
|
133
|
+
const content = readFileSync(versionFile, "utf8");
|
|
134
|
+
const version = content.match(/m_EditorVersion:\s*([^\s\n]+)/)?.[1];
|
|
135
|
+
if (!version) {
|
|
136
|
+
throw new Error(`Could not extract Unity version from ${versionFile}`);
|
|
137
|
+
}
|
|
138
|
+
return version;
|
|
139
|
+
}
|
|
140
|
+
function getUnityPathWindows(version) {
|
|
141
|
+
const candidates = [];
|
|
142
|
+
const programFiles = process.env["PROGRAMFILES"];
|
|
143
|
+
const programFilesX86 = process.env["PROGRAMFILES(X86)"];
|
|
144
|
+
const localAppData = process.env["LOCALAPPDATA"];
|
|
145
|
+
const addCandidate = (base) => {
|
|
146
|
+
if (!base) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
candidates.push(join(base, "Unity", "Hub", "Editor", version, "Editor", "Unity.exe"));
|
|
150
|
+
};
|
|
151
|
+
addCandidate(programFiles);
|
|
152
|
+
addCandidate(programFilesX86);
|
|
153
|
+
addCandidate(localAppData);
|
|
154
|
+
candidates.push(join("C:\\", "Program Files", "Unity", "Hub", "Editor", version, "Editor", "Unity.exe"));
|
|
155
|
+
for (const candidate of candidates) {
|
|
156
|
+
if (existsSync(candidate)) {
|
|
157
|
+
return candidate;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return candidates[0] ?? join("C:\\", "Program Files", "Unity", "Hub", "Editor", version, "Editor", "Unity.exe");
|
|
161
|
+
}
|
|
162
|
+
function getUnityPath(version) {
|
|
163
|
+
if (process.platform === "darwin") {
|
|
164
|
+
return `/Applications/Unity/Hub/Editor/${version}/Unity.app/Contents/MacOS/Unity`;
|
|
165
|
+
}
|
|
166
|
+
if (process.platform === "win32") {
|
|
167
|
+
return getUnityPathWindows(version);
|
|
168
|
+
}
|
|
169
|
+
return `/Applications/Unity/Hub/Editor/${version}/Unity.app/Contents/MacOS/Unity`;
|
|
170
|
+
}
|
|
171
|
+
const removeTrailingSeparators = (target) => {
|
|
172
|
+
let trimmed = target;
|
|
173
|
+
while (trimmed.length > 1 && (trimmed.endsWith("/") || trimmed.endsWith("\\"))) {
|
|
174
|
+
trimmed = trimmed.slice(0, -1);
|
|
175
|
+
}
|
|
176
|
+
return trimmed;
|
|
177
|
+
};
|
|
178
|
+
const normalizePath = (target) => {
|
|
179
|
+
const resolvedPath = resolve(target);
|
|
180
|
+
const trimmed = removeTrailingSeparators(resolvedPath);
|
|
181
|
+
return trimmed;
|
|
182
|
+
};
|
|
183
|
+
const toComparablePath = (value) => {
|
|
184
|
+
return value.replace(/\\/g, "/").toLocaleLowerCase();
|
|
185
|
+
};
|
|
186
|
+
const pathsEqual = (left, right) => {
|
|
187
|
+
return toComparablePath(normalizePath(left)) === toComparablePath(normalizePath(right));
|
|
188
|
+
};
|
|
189
|
+
function extractProjectPath(command) {
|
|
190
|
+
const match = command.match(PROJECT_PATH_PATTERN);
|
|
191
|
+
if (!match) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
const raw = match[1];
|
|
195
|
+
if (!raw) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
const trimmed = raw.trim();
|
|
199
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
|
|
200
|
+
return trimmed.slice(1, -1);
|
|
201
|
+
}
|
|
202
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
203
|
+
return trimmed.slice(1, -1);
|
|
204
|
+
}
|
|
205
|
+
return trimmed;
|
|
206
|
+
}
|
|
207
|
+
const isUnityAuxiliaryProcess = (command) => {
|
|
208
|
+
const normalizedCommand = command.toLowerCase();
|
|
209
|
+
if (normalizedCommand.includes("-batchmode")) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return normalizedCommand.includes("assetimportworker");
|
|
213
|
+
};
|
|
214
|
+
async function listUnityProcessesMac() {
|
|
215
|
+
let stdout = "";
|
|
216
|
+
try {
|
|
217
|
+
const result = await execFileAsync(PROCESS_LIST_COMMAND_MAC, PROCESS_LIST_ARGS_MAC);
|
|
218
|
+
stdout = result.stdout;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
222
|
+
console.error(`Failed to retrieve Unity process list: ${message}`);
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const lines = stdout
|
|
226
|
+
.split("\n")
|
|
227
|
+
.map((line) => line.trim())
|
|
228
|
+
.filter((line) => line.length > 0);
|
|
229
|
+
const processes = [];
|
|
230
|
+
for (const line of lines) {
|
|
231
|
+
const match = line.match(/^(\d+)\s+(.*)$/);
|
|
232
|
+
if (!match) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const pidValue = Number.parseInt(match[1] ?? "", 10);
|
|
236
|
+
if (!Number.isFinite(pidValue)) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const command = match[2] ?? "";
|
|
240
|
+
if (!UNITY_EXECUTABLE_PATTERN_MAC.test(command)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (isUnityAuxiliaryProcess(command)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const projectArgument = extractProjectPath(command);
|
|
247
|
+
if (!projectArgument) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
processes.push({
|
|
251
|
+
pid: pidValue,
|
|
252
|
+
projectPath: normalizePath(projectArgument),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return processes;
|
|
256
|
+
}
|
|
257
|
+
async function listUnityProcessesWindows() {
|
|
258
|
+
const scriptLines = [
|
|
259
|
+
"$ErrorActionPreference = 'Stop'",
|
|
260
|
+
"$processes = Get-CimInstance Win32_Process -Filter \"Name = 'Unity.exe'\" | Where-Object { $_.CommandLine }",
|
|
261
|
+
"foreach ($process in $processes) {",
|
|
262
|
+
" $commandLine = $process.CommandLine -replace \"`r\", ' ' -replace \"`n\", ' '",
|
|
263
|
+
" Write-Output (\"{0}|{1}\" -f $process.ProcessId, $commandLine)",
|
|
264
|
+
"}",
|
|
265
|
+
];
|
|
266
|
+
let stdout = "";
|
|
267
|
+
try {
|
|
268
|
+
const result = await execFileAsync(WINDOWS_POWERSHELL, ["-NoProfile", "-Command", scriptLines.join("\n")]);
|
|
269
|
+
stdout = result.stdout ?? "";
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
273
|
+
console.error(`Failed to retrieve Unity process list on Windows: ${message}`);
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
const lines = stdout
|
|
277
|
+
.split("\n")
|
|
278
|
+
.map((line) => line.trim())
|
|
279
|
+
.filter((line) => line.length > 0);
|
|
280
|
+
const processes = [];
|
|
281
|
+
for (const line of lines) {
|
|
282
|
+
const delimiterIndex = line.indexOf("|");
|
|
283
|
+
if (delimiterIndex < 0) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const pidText = line.slice(0, delimiterIndex).trim();
|
|
287
|
+
const command = line.slice(delimiterIndex + 1).trim();
|
|
288
|
+
const pidValue = Number.parseInt(pidText, 10);
|
|
289
|
+
if (!Number.isFinite(pidValue)) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (!UNITY_EXECUTABLE_PATTERN_WINDOWS.test(command)) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (isUnityAuxiliaryProcess(command)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const projectArgument = extractProjectPath(command);
|
|
299
|
+
if (!projectArgument) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
processes.push({
|
|
303
|
+
pid: pidValue,
|
|
304
|
+
projectPath: normalizePath(projectArgument),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return processes;
|
|
308
|
+
}
|
|
309
|
+
async function listUnityProcesses() {
|
|
310
|
+
if (process.platform === "darwin") {
|
|
311
|
+
return await listUnityProcessesMac();
|
|
312
|
+
}
|
|
313
|
+
if (process.platform === "win32") {
|
|
314
|
+
return await listUnityProcessesWindows();
|
|
315
|
+
}
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
export async function findRunningUnityProcess(projectPath) {
|
|
319
|
+
const normalizedTarget = normalizePath(projectPath);
|
|
320
|
+
const processes = await listUnityProcesses();
|
|
321
|
+
return processes.find((candidate) => pathsEqual(candidate.projectPath, normalizedTarget));
|
|
322
|
+
}
|
|
323
|
+
export async function focusUnityProcess(pid) {
|
|
324
|
+
if (process.platform === "darwin") {
|
|
325
|
+
await focusUnityProcessMac(pid);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (process.platform === "win32") {
|
|
329
|
+
await focusUnityProcessWindows(pid);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function focusUnityProcessMac(pid) {
|
|
333
|
+
const script = `tell application "System Events" to set frontmost of (first process whose unix id is ${pid}) to true`;
|
|
334
|
+
try {
|
|
335
|
+
await execFileAsync("osascript", ["-e", script]);
|
|
336
|
+
console.log("Brought existing Unity to the front.");
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
340
|
+
console.warn(`Failed to bring Unity to front: ${message}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function focusUnityProcessWindows(pid) {
|
|
344
|
+
const addTypeLines = [
|
|
345
|
+
"Add-Type -TypeDefinition @\"",
|
|
346
|
+
"using System;",
|
|
347
|
+
"using System.Runtime.InteropServices;",
|
|
348
|
+
"public static class Win32Interop {",
|
|
349
|
+
" [DllImport(\"user32.dll\")] public static extern bool SetForegroundWindow(IntPtr hWnd);",
|
|
350
|
+
" [DllImport(\"user32.dll\")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);",
|
|
351
|
+
"}",
|
|
352
|
+
"\"@",
|
|
353
|
+
];
|
|
354
|
+
const scriptLines = [
|
|
355
|
+
"$ErrorActionPreference = 'Stop'",
|
|
356
|
+
...addTypeLines,
|
|
357
|
+
`try { $process = Get-Process -Id ${pid} -ErrorAction Stop } catch { return }`,
|
|
358
|
+
"$handle = $process.MainWindowHandle",
|
|
359
|
+
"if ($handle -eq 0) { return }",
|
|
360
|
+
"[Win32Interop]::ShowWindowAsync($handle, 9) | Out-Null",
|
|
361
|
+
"[Win32Interop]::SetForegroundWindow($handle) | Out-Null",
|
|
362
|
+
];
|
|
363
|
+
try {
|
|
364
|
+
await execFileAsync(WINDOWS_POWERSHELL, ["-NoProfile", "-Command", scriptLines.join("\n")]);
|
|
365
|
+
console.log("Brought existing Unity to the front.");
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
369
|
+
console.warn(`Failed to bring Unity to front on Windows: ${message}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
export async function handleStaleLockfile(projectPath) {
|
|
373
|
+
const tempDirectoryPath = join(projectPath, TEMP_DIRECTORY_NAME);
|
|
374
|
+
const lockfilePath = join(tempDirectoryPath, UNITY_LOCKFILE_NAME);
|
|
375
|
+
if (!existsSync(lockfilePath)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
console.log(`UnityLockfile found without active Unity process: ${lockfilePath}`);
|
|
379
|
+
console.log("Assuming previous crash. Cleaning Temp directory and continuing launch.");
|
|
380
|
+
try {
|
|
381
|
+
await rm(tempDirectoryPath, { recursive: true, force: true });
|
|
382
|
+
console.log("Deleted Temp directory.");
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
386
|
+
console.warn(`Failed to delete Temp directory: ${message}`);
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
await rm(lockfilePath, { force: true });
|
|
390
|
+
console.log("Deleted UnityLockfile.");
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
394
|
+
console.warn(`Failed to delete UnityLockfile: ${message}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const KILL_POLL_INTERVAL_MS = 100;
|
|
398
|
+
const KILL_TIMEOUT_MS = 10000;
|
|
399
|
+
function isProcessAlive(pid) {
|
|
400
|
+
try {
|
|
401
|
+
process.kill(pid, 0);
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function killProcess(pid) {
|
|
409
|
+
try {
|
|
410
|
+
process.kill(pid, "SIGKILL");
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Process already exited
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async function waitForProcessExit(pid) {
|
|
417
|
+
const start = Date.now();
|
|
418
|
+
while (Date.now() - start < KILL_TIMEOUT_MS) {
|
|
419
|
+
if (!isProcessAlive(pid)) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
await new Promise((resolve) => setTimeout(resolve, KILL_POLL_INTERVAL_MS));
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
export async function killRunningUnity(projectPath) {
|
|
427
|
+
const processInfo = await findRunningUnityProcess(projectPath);
|
|
428
|
+
if (!processInfo) {
|
|
429
|
+
console.log("No running Unity process found for this project.");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const pid = processInfo.pid;
|
|
433
|
+
console.log(`Killing Unity (PID: ${pid})...`);
|
|
434
|
+
killProcess(pid);
|
|
435
|
+
const exited = await waitForProcessExit(pid);
|
|
436
|
+
if (!exited) {
|
|
437
|
+
throw new Error(`Failed to kill Unity (PID: ${pid}) within ${KILL_TIMEOUT_MS / 1000}s.`);
|
|
438
|
+
}
|
|
439
|
+
console.log("Unity killed.");
|
|
440
|
+
}
|
|
441
|
+
function hasBuildTargetArg(unityArgs) {
|
|
442
|
+
for (const arg of unityArgs) {
|
|
443
|
+
if (arg === "-buildTarget") {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
if (arg.startsWith("-buildTarget=")) {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
const EXCLUDED_DIR_NAMES = new Set([
|
|
453
|
+
"library",
|
|
454
|
+
"temp",
|
|
455
|
+
"logs",
|
|
456
|
+
"obj",
|
|
457
|
+
".git",
|
|
458
|
+
"node_modules",
|
|
459
|
+
".idea",
|
|
460
|
+
".vscode",
|
|
461
|
+
".vs",
|
|
462
|
+
]);
|
|
463
|
+
function isUnityProjectRoot(candidateDir) {
|
|
464
|
+
const versionFile = join(candidateDir, "ProjectSettings", "ProjectVersion.txt");
|
|
465
|
+
return existsSync(versionFile);
|
|
466
|
+
}
|
|
467
|
+
function listSubdirectoriesSorted(dir) {
|
|
468
|
+
let entries = [];
|
|
469
|
+
try {
|
|
470
|
+
const dirents = readdirSync(dir, { withFileTypes: true });
|
|
471
|
+
const subdirs = dirents
|
|
472
|
+
.filter((d) => d.isDirectory())
|
|
473
|
+
.map((d) => d.name)
|
|
474
|
+
.filter((name) => !EXCLUDED_DIR_NAMES.has(name.toLocaleLowerCase()));
|
|
475
|
+
subdirs.sort((a, b) => a.localeCompare(b));
|
|
476
|
+
entries = subdirs.map((name) => join(dir, name));
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Ignore directories we cannot read
|
|
480
|
+
entries = [];
|
|
481
|
+
}
|
|
482
|
+
return entries;
|
|
483
|
+
}
|
|
484
|
+
export function findUnityProjectBfs(rootDir, maxDepth) {
|
|
485
|
+
const queue = [];
|
|
486
|
+
let rootCanonical;
|
|
487
|
+
try {
|
|
488
|
+
rootCanonical = realpathSync(rootDir);
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
rootCanonical = rootDir;
|
|
492
|
+
}
|
|
493
|
+
queue.push({ dir: rootCanonical, depth: 0 });
|
|
494
|
+
const visited = new Set([toComparablePath(normalizePath(rootCanonical))]);
|
|
495
|
+
while (queue.length > 0) {
|
|
496
|
+
const current = queue.shift();
|
|
497
|
+
if (!current) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const { dir, depth } = current;
|
|
501
|
+
if (isUnityProjectRoot(dir)) {
|
|
502
|
+
return normalizePath(dir);
|
|
503
|
+
}
|
|
504
|
+
const canDescend = maxDepth === -1 || depth < maxDepth;
|
|
505
|
+
if (!canDescend) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const children = listSubdirectoriesSorted(dir);
|
|
509
|
+
for (const child of children) {
|
|
510
|
+
let childCanonical = child;
|
|
511
|
+
try {
|
|
512
|
+
const stat = lstatSync(child);
|
|
513
|
+
if (stat.isSymbolicLink()) {
|
|
514
|
+
try {
|
|
515
|
+
childCanonical = realpathSync(child);
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
// Broken symlink: skip
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const key = toComparablePath(normalizePath(childCanonical));
|
|
527
|
+
if (visited.has(key)) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
visited.add(key);
|
|
531
|
+
queue.push({ dir: childCanonical, depth: depth + 1 });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
export async function launch(opts) {
|
|
537
|
+
const { projectPath, platform, unityArgs, unityVersion } = opts;
|
|
538
|
+
const unityPath = getUnityPath(unityVersion);
|
|
539
|
+
console.log(`Detected Unity version: ${unityVersion}`);
|
|
540
|
+
if (!existsSync(unityPath)) {
|
|
541
|
+
throw new Error(`Unity ${unityVersion} not found at ${unityPath}. Please install Unity through Unity Hub.`);
|
|
542
|
+
}
|
|
543
|
+
console.log("Opening Unity...");
|
|
544
|
+
console.log(`Project Path: ${projectPath}`);
|
|
545
|
+
const args = ["-projectPath", projectPath];
|
|
546
|
+
const unityArgsContainBuildTarget = hasBuildTargetArg(unityArgs);
|
|
547
|
+
if (platform && platform.length > 0 && !unityArgsContainBuildTarget) {
|
|
548
|
+
args.push("-buildTarget", platform);
|
|
549
|
+
}
|
|
550
|
+
const hubCliArgs = await getProjectCliArgs(projectPath);
|
|
551
|
+
if (hubCliArgs.length > 0) {
|
|
552
|
+
args.push(...hubCliArgs);
|
|
553
|
+
}
|
|
554
|
+
if (unityArgs.length > 0) {
|
|
555
|
+
args.push(...unityArgs);
|
|
556
|
+
}
|
|
557
|
+
const child = spawn(unityPath, args, { stdio: "ignore", detached: true });
|
|
558
|
+
child.unref();
|
|
559
|
+
}
|
|
560
|
+
// Re-export Unity Hub functions
|
|
561
|
+
export { ensureProjectEntryAndUpdate, updateLastModifiedIfExists, getProjectCliArgs, parseCliArgs };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const ensureProjectEntryAndUpdate: (projectPath: string, version: string, when: Date, setFavorite?: boolean) => Promise<void>;
|
|
2
|
+
export declare const updateLastModifiedIfExists: (projectPath: string, when: Date) => Promise<void>;
|
|
3
|
+
export declare const parseCliArgs: (cliArgsString: string) => string[];
|
|
4
|
+
export declare const getProjectCliArgs: (projectPath: string) => Promise<string[]>;
|
|
5
|
+
//# sourceMappingURL=unityHub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unityHub.d.ts","sourceRoot":"","sources":["../src/unityHub.ts"],"names":[],"mappings":"AAwFA,eAAO,MAAM,2BAA2B,GACtC,aAAa,MAAM,EACnB,SAAS,MAAM,EACf,MAAM,IAAI,EACV,qBAAmB,KAClB,OAAO,CAAC,IAAI,CAgEd,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,aAAa,MAAM,EACnB,MAAM,IAAI,KACT,OAAO,CAAC,IAAI,CAuDd,CAAC;AAoBF,eAAO,MAAM,YAAY,GAAI,eAAe,MAAM,KAAG,MAAM,EA6C1D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CA8C7E,CAAC"}
|
package/dist/unityHub.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import assert from "node:assert";
|
|
4
5
|
const resolveUnityHubProjectFiles = () => {
|
|
5
6
|
if (process.platform === "darwin") {
|
|
6
7
|
const home = process.env.HOME;
|
|
@@ -168,3 +169,99 @@ export const updateLastModifiedIfExists = async (projectPath, when) => {
|
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
};
|
|
172
|
+
const resolveUnityHubProjectsInfoFile = () => {
|
|
173
|
+
if (process.platform === "darwin") {
|
|
174
|
+
const home = process.env.HOME;
|
|
175
|
+
if (!home) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
return join(home, "Library", "Application Support", "UnityHub", "projectsInfo.json");
|
|
179
|
+
}
|
|
180
|
+
if (process.platform === "win32") {
|
|
181
|
+
const appData = process.env.APPDATA;
|
|
182
|
+
if (!appData) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
return join(appData, "UnityHub", "projectsInfo.json");
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
};
|
|
189
|
+
export const parseCliArgs = (cliArgsString) => {
|
|
190
|
+
assert(cliArgsString !== null && cliArgsString !== undefined, "cliArgsString must not be null");
|
|
191
|
+
const trimmed = cliArgsString.trim();
|
|
192
|
+
if (trimmed.length === 0) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
const tokens = [];
|
|
196
|
+
let current = "";
|
|
197
|
+
let inQuote = null;
|
|
198
|
+
for (const char of trimmed) {
|
|
199
|
+
if (inQuote !== null) {
|
|
200
|
+
if (char === inQuote) {
|
|
201
|
+
tokens.push(current);
|
|
202
|
+
current = "";
|
|
203
|
+
inQuote = null;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
current += char;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (char === '"' || char === "'") {
|
|
211
|
+
inQuote = char;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (char === " ") {
|
|
215
|
+
if (current.length > 0) {
|
|
216
|
+
tokens.push(current);
|
|
217
|
+
current = "";
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
current += char;
|
|
222
|
+
}
|
|
223
|
+
if (current.length > 0) {
|
|
224
|
+
tokens.push(current);
|
|
225
|
+
}
|
|
226
|
+
return tokens;
|
|
227
|
+
};
|
|
228
|
+
export const getProjectCliArgs = async (projectPath) => {
|
|
229
|
+
assert(projectPath !== null && projectPath !== undefined, "projectPath must not be null");
|
|
230
|
+
const infoFilePath = resolveUnityHubProjectsInfoFile();
|
|
231
|
+
if (!infoFilePath) {
|
|
232
|
+
logDebug("projectsInfo.json path could not be resolved.");
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
logDebug(`Reading projectsInfo.json: ${infoFilePath}`);
|
|
236
|
+
let content;
|
|
237
|
+
try {
|
|
238
|
+
content = await readFile(infoFilePath, "utf8");
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
logDebug("projectsInfo.json not found or not readable.");
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
let json;
|
|
245
|
+
try {
|
|
246
|
+
json = JSON.parse(content);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
logDebug("projectsInfo.json parse failed.");
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
const normalizedProjectPath = normalizePath(projectPath);
|
|
253
|
+
const projectKey = Object.keys(json).find((key) => pathsEqual(key, normalizedProjectPath));
|
|
254
|
+
if (!projectKey) {
|
|
255
|
+
logDebug(`No entry found for project: ${normalizedProjectPath}`);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
const projectInfo = json[projectKey];
|
|
259
|
+
const cliArgsString = projectInfo?.cliArgs;
|
|
260
|
+
if (!cliArgsString || cliArgsString.trim().length === 0) {
|
|
261
|
+
logDebug("cliArgs is empty or not defined.");
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
const parsed = parseCliArgs(cliArgsString);
|
|
265
|
+
logDebug(`Parsed Unity Hub cliArgs: ${JSON.stringify(parsed)}`);
|
|
266
|
+
return parsed;
|
|
267
|
+
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "launch-unity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Open a Unity project with the matching Editor version (macOS/Windows)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"bin": {
|
|
8
15
|
"launch-unity": "dist/launch.js"
|
|
9
16
|
},
|
|
@@ -38,16 +45,16 @@
|
|
|
38
45
|
"node": ">=18"
|
|
39
46
|
},
|
|
40
47
|
"devDependencies": {
|
|
41
|
-
"@types/node": "25.0
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
43
|
-
"@typescript-eslint/parser": "8.
|
|
48
|
+
"@types/node": "25.2.0",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "8.54.0",
|
|
50
|
+
"@typescript-eslint/parser": "8.54.0",
|
|
44
51
|
"eslint": "9.39.2",
|
|
45
52
|
"eslint-config-prettier": "10.1.8",
|
|
46
|
-
"prettier": "3.
|
|
53
|
+
"prettier": "3.8.1",
|
|
47
54
|
"typescript": "5.9.3"
|
|
48
55
|
},
|
|
49
56
|
"dependencies": {
|
|
50
|
-
"typescript-eslint": "8.
|
|
57
|
+
"typescript-eslint": "8.54.0"
|
|
51
58
|
},
|
|
52
59
|
"overrides": {
|
|
53
60
|
"js-yaml": "4.1.1"
|