launch-unity 0.6.1 → 0.7.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 +1 -17
- package/README.md +1 -14
- package/dist/launch.js +63 -7
- package/package.json +2 -3
- package/dist/quit.js +0 -186
package/README.ja.md
CHANGED
|
@@ -18,7 +18,7 @@ npx launch-unity
|
|
|
18
18
|
## 使用例
|
|
19
19
|
```bash
|
|
20
20
|
# NPX(推奨 / インストール不要)
|
|
21
|
-
npx launch-unity #
|
|
21
|
+
npx launch-unity # カレントディレクトリから3階層下までUnityプロジェクトを探索して開く
|
|
22
22
|
npx launch-unity /path/to/Proj # プロジェクトを指定
|
|
23
23
|
npx launch-unity /path Android # ビルドターゲットを指定
|
|
24
24
|
npx -y launch-unity # npx の「Ok to proceed?」確認をスキップ
|
|
@@ -31,11 +31,6 @@ npx launch-unity /path Android -- -executeMethod My.Build.Entry
|
|
|
31
31
|
launch-unity
|
|
32
32
|
launch-unity /path/to/MyUnityProject Android
|
|
33
33
|
launch-unity . -- -buildTarget iOS -projectPath . # 上書きも可能
|
|
34
|
-
|
|
35
|
-
# プロジェクトを開いている Unity を終了
|
|
36
|
-
quit-unity # カレントディレクトリのプロジェクトの Unity を終了
|
|
37
|
-
quit-unity /path/to/Proj # 指定プロジェクトの Unity を終了
|
|
38
|
-
quit-unity . --timeout 20000 --force
|
|
39
34
|
```
|
|
40
35
|
|
|
41
36
|
指定した Unity プロジェクトの `ProjectSettings/ProjectVersion.txt` から必要な Unity Editor のバージョンを読み取り、
|
|
@@ -61,17 +56,6 @@ Unity Hub でインストール済みの該当バージョンを起動する mac
|
|
|
61
56
|
- macOS / Windows: Unity Hub のデフォルトインストールパスを前提にサポート。
|
|
62
57
|
- Linux: 未対応です。対応 PR を歓迎します。
|
|
63
58
|
|
|
64
|
-
## quit-unity 使い方(詳細)
|
|
65
|
-
```bash
|
|
66
|
-
# 基本構文
|
|
67
|
-
quit-unity [PROJECT_PATH] [--timeout <ms>] [--force]
|
|
68
|
-
|
|
69
|
-
# フラグ
|
|
70
|
-
# -t, --timeout <ms> 正常終了を待つ時間(既定: 15000ms)
|
|
71
|
-
# -f, --force タイムアウト後に強制終了
|
|
72
|
-
# -h, --help ヘルプ
|
|
73
|
-
```
|
|
74
|
-
|
|
75
59
|
## セキュリティ
|
|
76
60
|
|
|
77
61
|
このプロジェクトはサプライチェーン攻撃対策を実施しています:
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ npx launch-unity
|
|
|
18
18
|
## Usage Examples
|
|
19
19
|
```bash
|
|
20
20
|
# NPX (recommended, zero install)
|
|
21
|
-
npx launch-unity #
|
|
21
|
+
npx launch-unity # Search up to 3 levels deep for a Unity project and open it
|
|
22
22
|
npx launch-unity /path/to/Proj # Open a specific project
|
|
23
23
|
npx launch-unity /path Android # Specify build target
|
|
24
24
|
npx -y launch-unity # Skip npx "Ok to proceed?" prompt
|
|
@@ -31,11 +31,6 @@ npx launch-unity /path Android -- -executeMethod My.Build.Entry
|
|
|
31
31
|
launch-unity
|
|
32
32
|
launch-unity /path/to/MyUnityProject Android
|
|
33
33
|
launch-unity . -- -buildTarget iOS -projectPath . # You can override
|
|
34
|
-
|
|
35
|
-
# Quit the Unity instance holding a project open
|
|
36
|
-
quit-unity # Quit Unity for current directory project
|
|
37
|
-
quit-unity /path/to/Proj # Quit Unity for a specific project
|
|
38
|
-
quit-unity . --timeout 20000 --force
|
|
39
34
|
```
|
|
40
35
|
|
|
41
36
|
A TypeScript CLI for macOS and Windows that reads the required Unity Editor version from
|
|
@@ -63,14 +58,6 @@ launch-unity [PROJECT_PATH] [PLATFORM]
|
|
|
63
58
|
|
|
64
59
|
# Flags
|
|
65
60
|
# -h, --help Show help
|
|
66
|
-
|
|
67
|
-
# Quit syntax
|
|
68
|
-
quit-unity [PROJECT_PATH] [--timeout <ms>] [--force]
|
|
69
|
-
|
|
70
|
-
# Flags (quit-unity)
|
|
71
|
-
# -t, --timeout <ms> Time to wait for graceful quit (default: 15000)
|
|
72
|
-
# -f, --force Force kill if not exited within timeout
|
|
73
|
-
# -h, --help Show help
|
|
74
61
|
```
|
|
75
62
|
|
|
76
63
|
|
package/dist/launch.js
CHANGED
|
@@ -25,12 +25,17 @@ function parseArgs(argv) {
|
|
|
25
25
|
const unityArgs = doubleDashIndex >= 0 ? args.slice(doubleDashIndex + 1) : [];
|
|
26
26
|
const positionals = [];
|
|
27
27
|
let maxDepth = 3; // default 3; -1 means unlimited
|
|
28
|
+
let restart = false;
|
|
28
29
|
for (let i = 0; i < cliArgs.length; i++) {
|
|
29
30
|
const arg = cliArgs[i] ?? "";
|
|
30
31
|
if (arg === "--help" || arg === "-h") {
|
|
31
32
|
printHelp();
|
|
32
33
|
process.exit(0);
|
|
33
34
|
}
|
|
35
|
+
if (arg === "-r" || arg === "--restart") {
|
|
36
|
+
restart = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
34
39
|
if (arg.startsWith("--max-depth")) {
|
|
35
40
|
const parts = arg.split("=");
|
|
36
41
|
if (parts.length === 2) {
|
|
@@ -79,7 +84,7 @@ function parseArgs(argv) {
|
|
|
79
84
|
projectPath = resolve(positionals[0] ?? "");
|
|
80
85
|
platform = String(positionals[1] ?? "");
|
|
81
86
|
}
|
|
82
|
-
const options = { unityArgs, searchMaxDepth: maxDepth };
|
|
87
|
+
const options = { unityArgs, searchMaxDepth: maxDepth, restart };
|
|
83
88
|
if (projectPath !== undefined) {
|
|
84
89
|
options.projectPath = projectPath;
|
|
85
90
|
}
|
|
@@ -104,6 +109,7 @@ Forwarding:
|
|
|
104
109
|
|
|
105
110
|
Flags:
|
|
106
111
|
-h, --help Show this help message
|
|
112
|
+
-r, --restart Kill running Unity and restart
|
|
107
113
|
--max-depth <N> Search depth when PROJECT_PATH is omitted (default 3, -1 unlimited)
|
|
108
114
|
`;
|
|
109
115
|
process.stdout.write(help);
|
|
@@ -386,6 +392,51 @@ async function handleStaleLockfile(projectPath) {
|
|
|
386
392
|
console.warn(`Failed to delete UnityLockfile: ${message}`);
|
|
387
393
|
}
|
|
388
394
|
}
|
|
395
|
+
const KILL_POLL_INTERVAL_MS = 100;
|
|
396
|
+
const KILL_TIMEOUT_MS = 10000;
|
|
397
|
+
function isProcessAlive(pid) {
|
|
398
|
+
try {
|
|
399
|
+
process.kill(pid, 0);
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function killProcess(pid) {
|
|
407
|
+
try {
|
|
408
|
+
process.kill(pid, "SIGKILL");
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Process already exited
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async function waitForProcessExit(pid) {
|
|
415
|
+
const start = Date.now();
|
|
416
|
+
while (Date.now() - start < KILL_TIMEOUT_MS) {
|
|
417
|
+
if (!isProcessAlive(pid)) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
await new Promise((resolve) => setTimeout(resolve, KILL_POLL_INTERVAL_MS));
|
|
421
|
+
}
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
async function killRunningUnity(projectPath) {
|
|
425
|
+
const processInfo = await findRunningUnityProcess(projectPath);
|
|
426
|
+
if (!processInfo) {
|
|
427
|
+
console.log("No running Unity process found for this project.");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const pid = processInfo.pid;
|
|
431
|
+
console.log(`Killing Unity (PID: ${pid})...`);
|
|
432
|
+
killProcess(pid);
|
|
433
|
+
const exited = await waitForProcessExit(pid);
|
|
434
|
+
if (!exited) {
|
|
435
|
+
console.error(`Error: Failed to kill Unity (PID: ${pid}) within ${KILL_TIMEOUT_MS / 1000}s.`);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
console.log("Unity killed.");
|
|
439
|
+
}
|
|
389
440
|
function hasBuildTargetArg(unityArgs) {
|
|
390
441
|
for (const arg of unityArgs) {
|
|
391
442
|
if (arg === "-buildTarget") {
|
|
@@ -526,12 +577,17 @@ async function main() {
|
|
|
526
577
|
resolvedProjectPath = found;
|
|
527
578
|
}
|
|
528
579
|
ensureProjectPath(resolvedProjectPath);
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
580
|
+
if (options.restart) {
|
|
581
|
+
await killRunningUnity(resolvedProjectPath);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
const runningProcess = await findRunningUnityProcess(resolvedProjectPath);
|
|
585
|
+
if (runningProcess) {
|
|
586
|
+
console.log(`Unity process already running for project: ${resolvedProjectPath} (PID: ${runningProcess.pid})`);
|
|
587
|
+
await focusUnityProcess(runningProcess.pid);
|
|
588
|
+
process.exit(0);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
535
591
|
}
|
|
536
592
|
await handleStaleLockfile(resolvedProjectPath);
|
|
537
593
|
const resolved = {
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "launch-unity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Open a Unity project with the matching Editor version (macOS/Windows)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/launch.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"launch-unity": "dist/launch.js"
|
|
9
|
-
"quit-unity": "dist/quit.js"
|
|
8
|
+
"launch-unity": "dist/launch.js"
|
|
10
9
|
},
|
|
11
10
|
"scripts": {
|
|
12
11
|
"build": "tsc -p tsconfig.json",
|
package/dist/quit.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/*
|
|
3
|
-
quit-unity: Quit the Unity Editor instance that has the specified project open.
|
|
4
|
-
Platforms: macOS, Windows
|
|
5
|
-
*/
|
|
6
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
-
import { rm } from "node:fs/promises";
|
|
8
|
-
import { join, resolve } from "node:path";
|
|
9
|
-
const TEMP_DIRECTORY_NAME = "Temp";
|
|
10
|
-
function parseArgs(argv) {
|
|
11
|
-
const defaultProjectPath = process.cwd();
|
|
12
|
-
const defaultTimeoutMs = 15000;
|
|
13
|
-
const defaultForce = false;
|
|
14
|
-
const args = argv.slice(2);
|
|
15
|
-
let projectPath = defaultProjectPath;
|
|
16
|
-
let timeoutMs = defaultTimeoutMs;
|
|
17
|
-
let force = defaultForce;
|
|
18
|
-
for (let i = 0; i < args.length; i++) {
|
|
19
|
-
const arg = args[i] ?? "";
|
|
20
|
-
if (arg === "--help" || arg === "-h") {
|
|
21
|
-
printHelp();
|
|
22
|
-
process.exit(0);
|
|
23
|
-
}
|
|
24
|
-
if (arg === "--force" || arg === "-f") {
|
|
25
|
-
force = true;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
if (arg === "--timeout" || arg === "-t") {
|
|
29
|
-
const value = args[i + 1];
|
|
30
|
-
if (!value || value.startsWith("-")) {
|
|
31
|
-
console.error("Error: --timeout requires a millisecond value");
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
const parsedValue = Number(value);
|
|
35
|
-
const parsed = parsedValue;
|
|
36
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
37
|
-
console.error("Error: --timeout must be a non-negative number (milliseconds)");
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
timeoutMs = parsed;
|
|
41
|
-
i++;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (arg.startsWith("-")) {
|
|
45
|
-
// Unknown flags are ignored to keep CLI permissive
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
// First positional = project path
|
|
49
|
-
projectPath = resolve(arg);
|
|
50
|
-
}
|
|
51
|
-
return { projectPath, timeoutMs, force };
|
|
52
|
-
}
|
|
53
|
-
function printHelp() {
|
|
54
|
-
const help = `
|
|
55
|
-
Usage: quit-unity [PROJECT_PATH] [--timeout <ms>] [--force]
|
|
56
|
-
|
|
57
|
-
Quit the Unity Editor instance that has PROJECT_PATH open.
|
|
58
|
-
|
|
59
|
-
Arguments:
|
|
60
|
-
PROJECT_PATH Optional. Defaults to current directory
|
|
61
|
-
|
|
62
|
-
Flags:
|
|
63
|
-
-t, --timeout <ms> Time to wait for graceful quit (default: 15000)
|
|
64
|
-
-f, --force Force kill if not exited within timeout
|
|
65
|
-
-h, --help Show this help message
|
|
66
|
-
`;
|
|
67
|
-
process.stdout.write(help);
|
|
68
|
-
}
|
|
69
|
-
function ensureProjectPath(projectPath) {
|
|
70
|
-
if (!existsSync(projectPath)) {
|
|
71
|
-
console.error(`Error: Project directory not found: ${projectPath}`);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function readPidFromEditorInstance(projectPath) {
|
|
76
|
-
const editorInstancePath = join(projectPath, "Library", "EditorInstance.json");
|
|
77
|
-
if (!existsSync(editorInstancePath))
|
|
78
|
-
return null;
|
|
79
|
-
try {
|
|
80
|
-
const content = readFileSync(editorInstancePath, "utf8");
|
|
81
|
-
const data = JSON.parse(content);
|
|
82
|
-
const candidateKeys = [
|
|
83
|
-
"process_id",
|
|
84
|
-
"processId",
|
|
85
|
-
"processID",
|
|
86
|
-
"pid",
|
|
87
|
-
"PID",
|
|
88
|
-
];
|
|
89
|
-
for (const key of candidateKeys) {
|
|
90
|
-
const value = data[key];
|
|
91
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
92
|
-
return value;
|
|
93
|
-
if (typeof value === "string" && /^\d+$/.test(value))
|
|
94
|
-
return Number(value);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// fallthrough
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
function isProcessAlive(pid) {
|
|
103
|
-
try {
|
|
104
|
-
// signal 0: does not actually send a signal, just tests for existence/permissions
|
|
105
|
-
process.kill(pid, 0);
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async function waitForExit(pid, timeoutMs) {
|
|
113
|
-
const start = Date.now();
|
|
114
|
-
const stepIntervalMs = 200;
|
|
115
|
-
const stepMs = stepIntervalMs;
|
|
116
|
-
while (Date.now() - start < timeoutMs) {
|
|
117
|
-
if (!isProcessAlive(pid))
|
|
118
|
-
return true;
|
|
119
|
-
await new Promise((r) => setTimeout(r, stepMs));
|
|
120
|
-
}
|
|
121
|
-
return !isProcessAlive(pid);
|
|
122
|
-
}
|
|
123
|
-
async function quitByPid(pid, force, timeoutMs) {
|
|
124
|
-
// Try graceful first
|
|
125
|
-
try {
|
|
126
|
-
process.kill(pid, "SIGTERM");
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
// If process already exited, consider it success
|
|
130
|
-
if (!isProcessAlive(pid))
|
|
131
|
-
return true;
|
|
132
|
-
// If we cannot send the signal and the process is alive, escalate when force is true
|
|
133
|
-
if (!force)
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
const graceful = await waitForExit(pid, timeoutMs);
|
|
137
|
-
if (graceful)
|
|
138
|
-
return true;
|
|
139
|
-
if (!force)
|
|
140
|
-
return false;
|
|
141
|
-
// Force kill
|
|
142
|
-
try {
|
|
143
|
-
process.kill(pid, "SIGKILL");
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
// ignore
|
|
147
|
-
}
|
|
148
|
-
// Give a short moment after force
|
|
149
|
-
return await waitForExit(pid, 2000);
|
|
150
|
-
}
|
|
151
|
-
async function removeTempDirectory(projectPath) {
|
|
152
|
-
const tempDirectoryPath = join(projectPath, TEMP_DIRECTORY_NAME);
|
|
153
|
-
if (!existsSync(tempDirectoryPath))
|
|
154
|
-
return;
|
|
155
|
-
try {
|
|
156
|
-
await rm(tempDirectoryPath, { recursive: true, force: true });
|
|
157
|
-
console.log(`Deleted Temp directory: ${tempDirectoryPath}`);
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
-
console.warn(`Failed to delete Temp directory: ${message}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
async function main() {
|
|
165
|
-
const options = parseArgs(process.argv);
|
|
166
|
-
ensureProjectPath(options.projectPath);
|
|
167
|
-
const pid = readPidFromEditorInstance(options.projectPath);
|
|
168
|
-
if (pid === null) {
|
|
169
|
-
console.error("Error: Could not find Unity PID. Is this project currently open in Unity? (Missing Library/EditorInstance.json)");
|
|
170
|
-
process.exit(1);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
console.log(`Attempting to quit Unity (pid: ${pid}) for project: ${options.projectPath}`);
|
|
174
|
-
const ok = await quitByPid(pid, options.force, options.timeoutMs);
|
|
175
|
-
if (!ok) {
|
|
176
|
-
console.error(`Failed to quit Unity (pid: ${pid}) within ${options.timeoutMs}ms.${options.force ? "" : " Try --force."}`);
|
|
177
|
-
process.exit(1);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
console.log("Unity has exited.");
|
|
181
|
-
await removeTempDirectory(options.projectPath);
|
|
182
|
-
}
|
|
183
|
-
main().catch((error) => {
|
|
184
|
-
console.error(error);
|
|
185
|
-
process.exit(1);
|
|
186
|
-
});
|