launch-unity 0.10.1 → 0.12.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/README.ja.md +14 -13
- package/README.md +15 -14
- 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 +53 -37
- package/dist/lib.d.ts +35 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +557 -0
- package/dist/unityHub.d.ts +3 -0
- package/dist/unityHub.d.ts.map +1 -0
- package/package.json +14 -7
package/README.ja.md
CHANGED
|
@@ -18,29 +18,30 @@ npx launch-unity
|
|
|
18
18
|
## 使い方
|
|
19
19
|
```bash
|
|
20
20
|
# 構文
|
|
21
|
-
launch-unity [OPTIONS] [PROJECT_PATH] [
|
|
21
|
+
launch-unity [OPTIONS] [PROJECT_PATH] [-- UNITY_ARGS...]
|
|
22
22
|
launch-unity update
|
|
23
23
|
|
|
24
24
|
# 引数
|
|
25
25
|
# PROJECT_PATH Unityプロジェクトのディレクトリ(省略時は3階層下まで探索)
|
|
26
|
-
# PLATFORM Unityの -buildTarget に渡す値(例: StandaloneOSX, Android, iOS)
|
|
27
26
|
|
|
28
27
|
# オプション
|
|
29
|
-
# -h, --help
|
|
30
|
-
# -r, --restart
|
|
28
|
+
# -h, --help ヘルプを表示
|
|
29
|
+
# -r, --restart Unityを再起動
|
|
30
|
+
# -p, --platform <P> Unityの -buildTarget に渡す値(例: StandaloneOSX, Android, iOS)
|
|
31
31
|
# -a, -u, --add-unity-hub, --unity-hub-entry
|
|
32
|
-
#
|
|
33
|
-
# -f, --favorite
|
|
32
|
+
# Unity Hub に登録(Unityは起動しない)
|
|
33
|
+
# -f, --favorite Unity Hub にお気に入りとして登録(Unityは起動しない)
|
|
34
34
|
|
|
35
35
|
# 例
|
|
36
|
-
npx launch-unity
|
|
37
|
-
npx launch-unity /path/to/Proj
|
|
38
|
-
npx launch-unity
|
|
39
|
-
npx launch-unity -
|
|
40
|
-
npx launch-unity -
|
|
41
|
-
npx launch-unity -
|
|
36
|
+
npx launch-unity # プロジェクトを探索して開く
|
|
37
|
+
npx launch-unity /path/to/Proj # 指定プロジェクトを開く
|
|
38
|
+
npx launch-unity -p Android # ビルドターゲットを指定
|
|
39
|
+
npx launch-unity /path -p Android # パスとビルドターゲットを指定
|
|
40
|
+
npx launch-unity -r # Unityを再起動
|
|
41
|
+
npx launch-unity -a # Unity Hub に登録のみ(Unityは起動しない)
|
|
42
|
+
npx launch-unity -f # Unity Hub にお気に入り登録(Unityは起動しない)
|
|
42
43
|
npx launch-unity . -- -batchmode -quit -nographics -logFile - # Unity引数を渡す
|
|
43
|
-
npx launch-unity /path Android -- -executeMethod My.Build.Entry
|
|
44
|
+
npx launch-unity /path -p Android -- -executeMethod My.Build.Entry
|
|
44
45
|
|
|
45
46
|
# 自己更新(npmグローバルインストール向け)
|
|
46
47
|
launch-unity update
|
package/README.md
CHANGED
|
@@ -18,29 +18,30 @@ npx launch-unity
|
|
|
18
18
|
## Usage
|
|
19
19
|
```bash
|
|
20
20
|
# Syntax
|
|
21
|
-
launch-unity [OPTIONS] [PROJECT_PATH] [
|
|
21
|
+
launch-unity [OPTIONS] [PROJECT_PATH] [-- UNITY_ARGS...]
|
|
22
22
|
launch-unity update
|
|
23
23
|
|
|
24
24
|
# Arguments
|
|
25
|
-
# PROJECT_PATH
|
|
26
|
-
# PLATFORM Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
|
|
25
|
+
# PROJECT_PATH Unity project directory (searches up to 3 levels deep if omitted)
|
|
27
26
|
|
|
28
27
|
# Options
|
|
29
|
-
# -h, --help
|
|
30
|
-
# -r, --restart
|
|
28
|
+
# -h, --help Show help
|
|
29
|
+
# -r, --restart Kill running Unity and restart
|
|
30
|
+
# -p, --platform <P> Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
|
|
31
31
|
# -a, -u, --add-unity-hub, --unity-hub-entry
|
|
32
|
-
#
|
|
33
|
-
# -f, --favorite
|
|
32
|
+
# Register to Unity Hub (does not launch Unity)
|
|
33
|
+
# -f, --favorite Register to Unity Hub as favorite (does not launch Unity)
|
|
34
34
|
|
|
35
35
|
# Examples
|
|
36
|
-
npx launch-unity
|
|
37
|
-
npx launch-unity /path/to/Proj
|
|
38
|
-
npx launch-unity
|
|
39
|
-
npx launch-unity -
|
|
40
|
-
npx launch-unity -
|
|
41
|
-
npx launch-unity -
|
|
36
|
+
npx launch-unity # Search for project and open
|
|
37
|
+
npx launch-unity /path/to/Proj # Open specific project
|
|
38
|
+
npx launch-unity -p Android # Specify build target
|
|
39
|
+
npx launch-unity /path -p Android # Specify path and build target
|
|
40
|
+
npx launch-unity -r # Restart Unity
|
|
41
|
+
npx launch-unity -a # Register to Unity Hub only (does not launch Unity)
|
|
42
|
+
npx launch-unity -f # Register as favorite (does not launch Unity)
|
|
42
43
|
npx launch-unity . -- -batchmode -quit -nographics -logFile - # Pass Unity args
|
|
43
|
-
npx launch-unity /path Android -- -executeMethod My.Build.Entry
|
|
44
|
+
npx launch-unity /path -p Android -- -executeMethod My.Build.Entry
|
|
44
45
|
|
|
45
46
|
# Self update (for npm global install)
|
|
46
47
|
launch-unity update
|
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): 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,wBAAgB,MAAM,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CA4BxD"}
|
package/dist/launch.js
CHANGED
|
@@ -7,7 +7,7 @@ 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
12
|
import { ensureProjectEntryAndUpdate, updateLastModifiedIfExists } from "./unityHub.js";
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
@@ -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;
|
|
@@ -146,6 +146,7 @@ function parseArgs(argv) {
|
|
|
146
146
|
let restart = false;
|
|
147
147
|
let addUnityHub = false;
|
|
148
148
|
let favoriteUnityHub = false;
|
|
149
|
+
let platform;
|
|
149
150
|
for (let i = 0; i < cliArgs.length; i++) {
|
|
150
151
|
const arg = cliArgs[i] ?? "";
|
|
151
152
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -171,6 +172,21 @@ function parseArgs(argv) {
|
|
|
171
172
|
favoriteUnityHub = true;
|
|
172
173
|
continue;
|
|
173
174
|
}
|
|
175
|
+
if (arg === "-p" || arg === "--platform") {
|
|
176
|
+
const next = cliArgs[i + 1];
|
|
177
|
+
if (typeof next === "string" && !next.startsWith("-")) {
|
|
178
|
+
platform = next;
|
|
179
|
+
i += 1;
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (arg.startsWith("--platform=")) {
|
|
184
|
+
const value = arg.slice("--platform=".length);
|
|
185
|
+
if (value.length > 0) {
|
|
186
|
+
platform = value;
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
174
190
|
if (arg.startsWith("--max-depth")) {
|
|
175
191
|
const parts = arg.split("=");
|
|
176
192
|
if (parts.length === 2) {
|
|
@@ -192,32 +208,19 @@ function parseArgs(argv) {
|
|
|
192
208
|
continue;
|
|
193
209
|
}
|
|
194
210
|
if (arg.startsWith("-")) {
|
|
211
|
+
console.warn(`Warning: Unknown option ignored: ${arg}`);
|
|
195
212
|
continue;
|
|
196
213
|
}
|
|
197
214
|
positionals.push(arg);
|
|
198
215
|
}
|
|
199
216
|
let projectPath;
|
|
200
|
-
|
|
201
|
-
if (positionals.length === 0) {
|
|
202
|
-
projectPath = undefined; // trigger search
|
|
203
|
-
platform = undefined;
|
|
204
|
-
}
|
|
205
|
-
else if (positionals.length === 1) {
|
|
206
|
-
const first = positionals[0] ?? "";
|
|
207
|
-
const resolvedFirst = resolve(first);
|
|
208
|
-
if (existsSync(resolvedFirst)) {
|
|
209
|
-
projectPath = resolvedFirst;
|
|
210
|
-
platform = undefined;
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
// Treat as platform when path does not exist
|
|
214
|
-
projectPath = undefined; // trigger search
|
|
215
|
-
platform = String(first);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
217
|
+
if (positionals.length > 0) {
|
|
219
218
|
projectPath = resolve(positionals[0] ?? "");
|
|
220
|
-
|
|
219
|
+
}
|
|
220
|
+
if (positionals.length > 1) {
|
|
221
|
+
const ignored = positionals.slice(1).join(", ");
|
|
222
|
+
console.warn(`Warning: Extra arguments ignored: ${ignored}`);
|
|
223
|
+
console.warn(" Use -p option for platform: launch-unity -p <platform>");
|
|
221
224
|
}
|
|
222
225
|
const options = {
|
|
223
226
|
unityArgs,
|
|
@@ -247,23 +250,23 @@ function getVersion() {
|
|
|
247
250
|
function printHelp() {
|
|
248
251
|
const help = `
|
|
249
252
|
Usage:
|
|
250
|
-
launch-unity [
|
|
253
|
+
launch-unity [OPTIONS] [PROJECT_PATH] [-- UNITY_ARGS...]
|
|
251
254
|
launch-unity update
|
|
252
255
|
|
|
253
256
|
Open a Unity project with the matching Unity Editor version installed by Unity Hub.
|
|
254
257
|
|
|
255
258
|
Arguments:
|
|
256
259
|
PROJECT_PATH Optional. If omitted, searches under the current directory (see --max-depth)
|
|
257
|
-
PLATFORM Optional. Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
|
|
258
260
|
|
|
259
261
|
Forwarding:
|
|
260
262
|
Everything after -- is forwarded to Unity unchanged.
|
|
261
|
-
If UNITY_ARGS includes -buildTarget, the
|
|
263
|
+
If UNITY_ARGS includes -buildTarget, the -p option is ignored.
|
|
262
264
|
|
|
263
|
-
|
|
265
|
+
Options:
|
|
264
266
|
-h, --help Show this help message
|
|
265
267
|
-v, --version Show version number
|
|
266
268
|
-r, --restart Kill running Unity and restart
|
|
269
|
+
-p, --platform <P> Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
|
|
267
270
|
--max-depth <N> Search depth when PROJECT_PATH is omitted (default 3, -1 unlimited)
|
|
268
271
|
-u, -a, --unity-hub-entry, --add-unity-hub
|
|
269
272
|
Add to Unity Hub if missing and update lastModified (does not launch Unity)
|
|
@@ -274,7 +277,7 @@ Commands:
|
|
|
274
277
|
`;
|
|
275
278
|
process.stdout.write(help);
|
|
276
279
|
}
|
|
277
|
-
function getUnityVersion(projectPath) {
|
|
280
|
+
export function getUnityVersion(projectPath) {
|
|
278
281
|
const versionFile = join(projectPath, "ProjectSettings", "ProjectVersion.txt");
|
|
279
282
|
if (!existsSync(versionFile)) {
|
|
280
283
|
console.error(`Error: ProjectVersion.txt not found at ${versionFile}`);
|
|
@@ -473,12 +476,12 @@ async function listUnityProcesses() {
|
|
|
473
476
|
}
|
|
474
477
|
return [];
|
|
475
478
|
}
|
|
476
|
-
async function findRunningUnityProcess(projectPath) {
|
|
479
|
+
export async function findRunningUnityProcess(projectPath) {
|
|
477
480
|
const normalizedTarget = normalizePath(projectPath);
|
|
478
481
|
const processes = await listUnityProcesses();
|
|
479
482
|
return processes.find((candidate) => pathsEqual(candidate.projectPath, normalizedTarget));
|
|
480
483
|
}
|
|
481
|
-
async function focusUnityProcess(pid) {
|
|
484
|
+
export async function focusUnityProcess(pid) {
|
|
482
485
|
if (process.platform === "darwin") {
|
|
483
486
|
await focusUnityProcessMac(pid);
|
|
484
487
|
return;
|
|
@@ -527,7 +530,7 @@ async function focusUnityProcessWindows(pid) {
|
|
|
527
530
|
console.warn(`Failed to bring Unity to front on Windows: ${message}`);
|
|
528
531
|
}
|
|
529
532
|
}
|
|
530
|
-
async function handleStaleLockfile(projectPath) {
|
|
533
|
+
export async function handleStaleLockfile(projectPath) {
|
|
531
534
|
const tempDirectoryPath = join(projectPath, TEMP_DIRECTORY_NAME);
|
|
532
535
|
const lockfilePath = join(tempDirectoryPath, UNITY_LOCKFILE_NAME);
|
|
533
536
|
if (!existsSync(lockfilePath)) {
|
|
@@ -581,7 +584,7 @@ async function waitForProcessExit(pid) {
|
|
|
581
584
|
}
|
|
582
585
|
return false;
|
|
583
586
|
}
|
|
584
|
-
async function killRunningUnity(projectPath) {
|
|
587
|
+
export async function killRunningUnity(projectPath) {
|
|
585
588
|
const processInfo = await findRunningUnityProcess(projectPath);
|
|
586
589
|
if (!processInfo) {
|
|
587
590
|
console.log("No running Unity process found for this project.");
|
|
@@ -640,7 +643,7 @@ function listSubdirectoriesSorted(dir) {
|
|
|
640
643
|
}
|
|
641
644
|
return entries;
|
|
642
645
|
}
|
|
643
|
-
function findUnityProjectBfs(rootDir, maxDepth) {
|
|
646
|
+
export function findUnityProjectBfs(rootDir, maxDepth) {
|
|
644
647
|
const queue = [];
|
|
645
648
|
let rootCanonical;
|
|
646
649
|
try {
|
|
@@ -692,7 +695,7 @@ function findUnityProjectBfs(rootDir, maxDepth) {
|
|
|
692
695
|
}
|
|
693
696
|
return undefined;
|
|
694
697
|
}
|
|
695
|
-
function launch(opts) {
|
|
698
|
+
export function launch(opts) {
|
|
696
699
|
const { projectPath, platform, unityArgs, unityVersion } = opts;
|
|
697
700
|
const unityPath = getUnityPath(unityVersion);
|
|
698
701
|
console.log(`Detected Unity version: ${unityVersion}`);
|
|
@@ -835,7 +838,20 @@ async function runSelfUpdate() {
|
|
|
835
838
|
}
|
|
836
839
|
process.exit(1);
|
|
837
840
|
}
|
|
838
|
-
main()
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
841
|
+
// Only run main() when this file is executed directly (not when imported as a library)
|
|
842
|
+
let isDirectExecution = false;
|
|
843
|
+
if (typeof process.argv[1] === "string") {
|
|
844
|
+
try {
|
|
845
|
+
isDirectExecution =
|
|
846
|
+
import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
isDirectExecution = false;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (isDirectExecution) {
|
|
853
|
+
main().catch((error) => {
|
|
854
|
+
console.error(error);
|
|
855
|
+
process.exit(1);
|
|
856
|
+
});
|
|
857
|
+
}
|
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 } 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): void;
|
|
34
|
+
export { ensureProjectEntryAndUpdate, updateLastModifiedIfExists };
|
|
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,MAAM,eAAe,CAAC;AAExF,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,wBAAgB,MAAM,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CA4BxD;AAGD,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,CAAC"}
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
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 } 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 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
|
+
if (unityArgs.length > 0) {
|
|
551
|
+
args.push(...unityArgs);
|
|
552
|
+
}
|
|
553
|
+
const child = spawn(unityPath, args, { stdio: "ignore", detached: true });
|
|
554
|
+
child.unref();
|
|
555
|
+
}
|
|
556
|
+
// Re-export Unity Hub functions
|
|
557
|
+
export { ensureProjectEntryAndUpdate, updateLastModifiedIfExists };
|
|
@@ -0,0 +1,3 @@
|
|
|
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
|
+
//# sourceMappingURL=unityHub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unityHub.d.ts","sourceRoot":"","sources":["../src/unityHub.ts"],"names":[],"mappings":"AAiFA,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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "launch-unity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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.0.10",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "8.53.0",
|
|
50
|
+
"@typescript-eslint/parser": "8.53.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.53.1"
|
|
51
58
|
},
|
|
52
59
|
"overrides": {
|
|
53
60
|
"js-yaml": "4.1.1"
|