launch-unity 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Masamichi Hatayama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ja.md ADDED
@@ -0,0 +1,50 @@
1
+ launch-unity
2
+ =================
3
+
4
+ [English](README.md) | [日本語](README.ja.md)
5
+
6
+ Unity Hubを仲介せず、コマンドラインからUnityを立ち上げます。
7
+
8
+ ## 使用例
9
+ ```bash
10
+ # NPX(推奨 / インストール不要)
11
+ npx launch-unity # カレントディレクトリを開く(ビルドターゲット未指定時はプロジェクトの現在のビルドターゲットを使用)
12
+ npx launch-unity /path/to/Proj # プロジェクトを指定
13
+ npx launch-unity /path Android # ビルドターゲットを指定
14
+ npx -y launch-unity # npx の「Ok to proceed?」確認をスキップ
15
+
16
+ # `--` 以降は Unity のコマンドライン引数をそのまま転送
17
+ npx launch-unity . -- -batchmode -quit -nographics -logFile -
18
+ npx launch-unity /path Android -- -executeMethod My.Build.Entry
19
+
20
+ # グローバルインストール後
21
+ launch-unity
22
+ launch-unity /path/to/MyUnityProject Android
23
+ launch-unity . -- -buildTarget iOS -projectPath . # 上書きも可能
24
+ ```
25
+
26
+ 指定した Unity プロジェクトの `ProjectSettings/ProjectVersion.txt` から必要な Unity Editor のバージョンを読み取り、
27
+ Unity Hub でインストール済みの該当バージョンを起動する macOS/Windows 向け TypeScript 製 CLI です。`Temp/UnityLockfile` が
28
+ 存在する場合は、削除して続行するかをターミナル上で確認します。
29
+
30
+ 既定で想定する Unity のパス:
31
+ - macOS: `/Applications/Unity/Hub/Editor/<version>/Unity.app/Contents/MacOS/Unity`
32
+ - Windows(検索対象):
33
+ - `%PROGRAMFILES%/Unity/Hub/Editor/<version>/Editor/Unity.exe`
34
+ - `%PROGRAMFILES(X86)%/Unity/Hub/Editor/<version>/Editor/Unity.exe`
35
+ - `%LOCALAPPDATA%/Unity/Hub/Editor/<version>/Editor/Unity.exe`
36
+ - `C:\\Program Files\\Unity\\Hub\\Editor\\<version>\\Editor\\Unity.exe`
37
+
38
+
39
+ ## トラブルシューティング
40
+ - エラー: `ProjectVersion.txt not found`
41
+ - 指定ディレクトリが Unity プロジェクト直下ではありません。プロジェクトのルートを指定してください。
42
+ - エラー: `Unity <version> not found`
43
+ - 該当バージョンの Unity を Unity Hub でインストールしてください。独自パスの場合は解決ロジックの修正を検討してください。
44
+
45
+ ## プラットフォーム注意事項
46
+ - macOS / Windows: Unity Hub のデフォルトインストールパスを前提にサポート。
47
+ - Linux: 未対応です。対応 PR を歓迎します。
48
+
49
+ ## ライセンス
50
+ - MIT。詳細は `LICENSE` を参照してください。
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ launch-unity
2
+ =================
3
+
4
+ [English](README.md) | [日本語](README.ja.md)
5
+
6
+ Without Unity Hub, launch Unity from the command line.
7
+
8
+ ## Usage Examples
9
+ ```bash
10
+ # NPX (recommended, zero install)
11
+ npx launch-unity # Open the current directory (if PLATFORM is omitted, uses the project's current build target)
12
+ npx launch-unity /path/to/Proj # Open a specific project
13
+ npx launch-unity /path Android # Specify build target
14
+ npx -y launch-unity # Skip npx "Ok to proceed?" prompt
15
+
16
+ # Pass through Unity CLI args with --
17
+ npx launch-unity . -- -batchmode -quit -nographics -logFile -
18
+ npx launch-unity /path Android -- -executeMethod My.Build.Entry
19
+
20
+ # Installed globally
21
+ launch-unity
22
+ launch-unity /path/to/MyUnityProject Android
23
+ launch-unity . -- -buildTarget iOS -projectPath . # You can override
24
+ ```
25
+
26
+ A TypeScript CLI for macOS and Windows that reads the required Unity Editor version from
27
+ `ProjectSettings/ProjectVersion.txt`, launches the matching Unity installed via Unity Hub,
28
+ and opens the project. If `Temp/UnityLockfile` exists, it asks in the terminal whether to
29
+ delete it before continuing.
30
+
31
+ Default Unity paths assumed:
32
+ - macOS: `/Applications/Unity/Hub/Editor/<version>/Unity.app/Contents/MacOS/Unity`
33
+ - Windows (searched):
34
+ - `%PROGRAMFILES%/Unity/Hub/Editor/<version>/Editor/Unity.exe`
35
+ - `%PROGRAMFILES(X86)%/Unity/Hub/Editor/<version>/Editor/Unity.exe`
36
+ - `%LOCALAPPDATA%/Unity/Hub/Editor/<version>/Editor/Unity.exe`
37
+ - `C:\\Program Files\\Unity\\Hub\\Editor\\<version>\\Editor\\Unity.exe`
38
+
39
+
40
+ ## Quick Setup
41
+ ```bash
42
+ # No install: use npx
43
+ npx launch-unity -h
44
+
45
+ # Optional: install globally after cloning
46
+ npm i -g .
47
+ ```
48
+
49
+
50
+ ## Detailed Usage
51
+ ```bash
52
+ # Basic syntax
53
+ launch-unity [PROJECT_PATH] [PLATFORM]
54
+
55
+ # Arguments
56
+ # - PROJECT_PATH (optional): Unity project directory. Defaults to current directory
57
+ # - PLATFORM (optional): Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
58
+
59
+ # Flags
60
+ # -h, --help Show help
61
+ ```
62
+
63
+
64
+ ## Troubleshooting
65
+ - Error: `ProjectVersion.txt not found`
66
+ - The provided directory is not a Unity project. Point to the project root.
67
+ - Error: `Unity <version> not found`
68
+ - Install the required version via Unity Hub, or adjust Unity path resolution.
69
+
70
+
71
+ ## Platform Notes
72
+ - macOS, Windows: Supported via Unity Hub default install paths.
73
+ - Linux: Not supported yet. Contributions are welcome.
74
+
75
+
76
+ ## License
77
+ - MIT. See `LICENSE` for details.
package/dist/cli.js ADDED
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ launch-unity: Open a Unity project with the matching Editor version.
4
+ Platforms: macOS, Windows
5
+ */
6
+ import { spawn } from "node:child_process";
7
+ import { existsSync, readFileSync, rmSync, createReadStream, createWriteStream } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ import readline from "node:readline";
10
+ function parseArgs(argv) {
11
+ const defaultProjectPath = process.cwd();
12
+ const args = argv.slice(2);
13
+ const doubleDashIndex = args.indexOf("--");
14
+ const cliArgs = doubleDashIndex >= 0 ? args.slice(0, doubleDashIndex) : args;
15
+ const unityArgs = doubleDashIndex >= 0 ? args.slice(doubleDashIndex + 1) : [];
16
+ const positionals = [];
17
+ for (const arg of cliArgs) {
18
+ if (arg === "--help" || arg === "-h") {
19
+ printHelp();
20
+ process.exit(0);
21
+ }
22
+ else if (arg.startsWith("-")) {
23
+ // Unknown flags are ignored to keep CLI permissive
24
+ continue;
25
+ }
26
+ else {
27
+ positionals.push(arg);
28
+ }
29
+ }
30
+ const projectPath = positionals[0] ? resolve(positionals[0]) : defaultProjectPath;
31
+ const platform = positionals[1] ? String(positionals[1]) : undefined;
32
+ const options = { projectPath, platform, unityArgs };
33
+ return options;
34
+ }
35
+ function printHelp() {
36
+ const help = `
37
+ Usage: launch-unity [PROJECT_PATH] [PLATFORM] -- [UNITY_ARGS...]
38
+
39
+ Open a Unity project with the matching Unity Editor version installed by Unity Hub.
40
+
41
+ Arguments:
42
+ PROJECT_PATH Optional. Defaults to current directory
43
+ PLATFORM Optional. Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
44
+
45
+ Forwarding:
46
+ Everything after -- is forwarded to Unity unchanged.
47
+ If UNITY_ARGS includes -buildTarget, the PLATFORM argument is ignored.
48
+
49
+ Flags:
50
+ -h, --help Show this help message
51
+ `;
52
+ process.stdout.write(help);
53
+ }
54
+ function getUnityVersion(projectPath) {
55
+ const versionFile = join(projectPath, "ProjectSettings", "ProjectVersion.txt");
56
+ if (!existsSync(versionFile)) {
57
+ console.error(`Error: ProjectVersion.txt not found at ${versionFile}`);
58
+ console.error("This does not appear to be a Unity project.");
59
+ process.exit(1);
60
+ }
61
+ const content = readFileSync(versionFile, "utf8");
62
+ const version = content.match(/m_EditorVersion:\s*([^\s\n]+)/)?.[1];
63
+ if (!version) {
64
+ console.error(`Error: Could not extract Unity version from ${versionFile}`);
65
+ process.exit(1);
66
+ }
67
+ return version;
68
+ }
69
+ function getUnityPathWindows(version) {
70
+ const candidates = [];
71
+ const programFiles = process.env["PROGRAMFILES"];
72
+ const programFilesX86 = process.env["PROGRAMFILES(X86)"];
73
+ const localAppData = process.env["LOCALAPPDATA"];
74
+ const addCandidate = (base) => {
75
+ if (!base)
76
+ return;
77
+ candidates.push(join(base, "Unity", "Hub", "Editor", version, "Editor", "Unity.exe"));
78
+ };
79
+ addCandidate(programFiles);
80
+ addCandidate(programFilesX86);
81
+ addCandidate(localAppData);
82
+ candidates.push(join("C:\\", "Program Files", "Unity", "Hub", "Editor", version, "Editor", "Unity.exe"));
83
+ for (const candidate of candidates) {
84
+ if (existsSync(candidate))
85
+ return candidate;
86
+ }
87
+ return candidates[0] ?? join("C:\\", "Program Files", "Unity", "Hub", "Editor", version, "Editor", "Unity.exe");
88
+ }
89
+ function getUnityPath(version) {
90
+ if (process.platform === "darwin") {
91
+ return `/Applications/Unity/Hub/Editor/${version}/Unity.app/Contents/MacOS/Unity`;
92
+ }
93
+ if (process.platform === "win32") {
94
+ return getUnityPathWindows(version);
95
+ }
96
+ return `/Applications/Unity/Hub/Editor/${version}/Unity.app/Contents/MacOS/Unity`;
97
+ }
98
+ function ensureProjectPath(projectPath) {
99
+ if (!existsSync(projectPath)) {
100
+ console.error(`Error: Project directory not found: ${projectPath}`);
101
+ process.exit(1);
102
+ }
103
+ }
104
+ function createPromptInterface() {
105
+ if (process.stdin.isTTY && process.stdout.isTTY) {
106
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
107
+ const close = () => rl.close();
108
+ return { rl, close };
109
+ }
110
+ try {
111
+ if (process.platform === "win32") {
112
+ const inCandidates = ["\\\\.\\CONIN$", "CONIN$"];
113
+ const outCandidates = ["\\\\.\\CONOUT$", "CONOUT$"];
114
+ for (const inPath of inCandidates) {
115
+ for (const outPath of outCandidates) {
116
+ try {
117
+ const input = createReadStream(inPath);
118
+ const output = createWriteStream(outPath);
119
+ const rl = readline.createInterface({ input, output });
120
+ const close = () => {
121
+ rl.close();
122
+ input.destroy();
123
+ output.end();
124
+ };
125
+ return { rl, close };
126
+ }
127
+ catch {
128
+ continue;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ else {
134
+ const input = createReadStream("/dev/tty");
135
+ const output = createWriteStream("/dev/tty");
136
+ const rl = readline.createInterface({ input, output });
137
+ const close = () => {
138
+ rl.close();
139
+ input.destroy();
140
+ output.end();
141
+ };
142
+ return { rl, close };
143
+ }
144
+ }
145
+ catch {
146
+ // fallthrough
147
+ }
148
+ return null;
149
+ }
150
+ async function handleUnityLockfilePrompt(lockfilePath) {
151
+ // Prefer single-key confirmation when a real TTY is available
152
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
153
+ const confirmedByKey = await promptYesNoSingleKey("Delete UnityLockfile and continue? Type 'y' to continue; anything else aborts: ");
154
+ if (!confirmedByKey) {
155
+ console.log("Aborted by user.");
156
+ return false;
157
+ }
158
+ rmSync(lockfilePath, { force: true });
159
+ console.log("Deleted UnityLockfile. Continuing launch.");
160
+ return true;
161
+ }
162
+ // Fallback to line-based prompt through OS console handles
163
+ const prompt = createPromptInterface();
164
+ if (!prompt) {
165
+ console.error("UnityLockfile exists. No interactive console available for confirmation.");
166
+ return false;
167
+ }
168
+ const confirmed = await new Promise((resolve) => {
169
+ prompt.rl.question("Delete UnityLockfile and continue? Type 'y' to continue; anything else aborts: ", (answer) => {
170
+ resolve(answer.trim() === "y");
171
+ });
172
+ });
173
+ prompt.close();
174
+ if (!confirmed) {
175
+ console.log("Aborted by user.");
176
+ return false;
177
+ }
178
+ rmSync(lockfilePath, { force: true });
179
+ console.log("Deleted UnityLockfile. Continuing launch.");
180
+ return true;
181
+ }
182
+ function stdinSupportsRawMode() {
183
+ const stdin = process.stdin;
184
+ return Boolean(stdin && stdin.isTTY && typeof stdin.setRawMode === "function" && process.stdout.isTTY);
185
+ }
186
+ async function promptYesNoSingleKey(message) {
187
+ if (!stdinSupportsRawMode())
188
+ return false;
189
+ const stdin = process.stdin;
190
+ return await new Promise((resolve) => {
191
+ const handleData = (data) => {
192
+ const firstByte = data[0] ?? 0;
193
+ const char = data.toString();
194
+ let result = false;
195
+ if (char === "y") {
196
+ result = true;
197
+ }
198
+ else if (firstByte === 3 /* Ctrl+C */ || firstByte === 27 /* ESC */ || char === "n" || char === "N" || firstByte === 13 /* Enter */) {
199
+ result = false;
200
+ }
201
+ else {
202
+ result = false;
203
+ }
204
+ process.stdout.write("\n");
205
+ cleanup();
206
+ resolve(result);
207
+ };
208
+ const cleanup = () => {
209
+ if (typeof stdin.setRawMode === "function")
210
+ stdin.setRawMode(false);
211
+ stdin.pause();
212
+ stdin.removeListener("data", handleData);
213
+ };
214
+ process.stdout.write(message);
215
+ if (typeof stdin.setRawMode === "function")
216
+ stdin.setRawMode(true);
217
+ stdin.resume();
218
+ stdin.once("data", handleData);
219
+ });
220
+ }
221
+ async function checkUnityRunning(projectPath) {
222
+ const lockfile = join(projectPath, "Temp", "UnityLockfile");
223
+ if (!existsSync(lockfile))
224
+ return true;
225
+ console.log(`UnityLockfile found: ${lockfile}`);
226
+ console.log("Another Unity process may be using this project.");
227
+ return await handleUnityLockfilePrompt(lockfile);
228
+ }
229
+ function hasBuildTargetArg(unityArgs) {
230
+ for (const arg of unityArgs) {
231
+ if (arg === "-buildTarget")
232
+ return true;
233
+ if (arg.startsWith("-buildTarget="))
234
+ return true;
235
+ }
236
+ return false;
237
+ }
238
+ function launch(opts) {
239
+ const { projectPath, platform, unityArgs } = opts;
240
+ const unityVersion = getUnityVersion(projectPath);
241
+ const unityPath = getUnityPath(unityVersion);
242
+ console.log(`Detected Unity version: ${unityVersion}`);
243
+ if (!existsSync(unityPath)) {
244
+ console.error(`Error: Unity ${unityVersion} not found at ${unityPath}`);
245
+ console.error("Please install Unity through Unity Hub.");
246
+ process.exit(1);
247
+ }
248
+ console.log("Opening Unity...");
249
+ console.log(`Project Path: ${projectPath}`);
250
+ const args = ["-projectPath", projectPath];
251
+ const unityArgsContainBuildTarget = hasBuildTargetArg(unityArgs);
252
+ if (platform && platform.length > 0 && !unityArgsContainBuildTarget) {
253
+ args.push("-buildTarget", platform);
254
+ }
255
+ if (unityArgs.length > 0) {
256
+ args.push(...unityArgs);
257
+ }
258
+ const child = spawn(unityPath, args, { stdio: "ignore", detached: true });
259
+ child.unref();
260
+ }
261
+ async function main() {
262
+ const options = parseArgs(process.argv);
263
+ ensureProjectPath(options.projectPath);
264
+ const ok = await checkUnityRunning(options.projectPath);
265
+ if (!ok) {
266
+ process.exit(0);
267
+ return;
268
+ }
269
+ launch(options);
270
+ }
271
+ main().catch((error) => {
272
+ console.error(error);
273
+ process.exit(1);
274
+ });
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "launch-unity",
3
+ "version": "0.1.0",
4
+ "description": "Open a Unity project with the matching Editor version (macOS/Windows)",
5
+ "type": "module",
6
+ "main": "dist/cli.js",
7
+ "bin": {
8
+ "launch-unity": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "start": "node dist/cli.js",
13
+ "lint": "eslint . --ext .ts,.js",
14
+ "lint:fix": "eslint . --ext .ts,.js --fix",
15
+ "format": "prettier --write .",
16
+ "prepack": "npm run build"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+ssh://git@github.com/hatayama/LaunchUnityCommand.git"
21
+ },
22
+ "keywords": [
23
+ "unity",
24
+ "unity-hub",
25
+ "cli",
26
+ "macos",
27
+ "windows"
28
+ ],
29
+ "author": "",
30
+ "license": "MIT",
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "README.ja.md",
35
+ "LICENSE"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.7.4",
42
+ "@typescript-eslint/eslint-plugin": "^8.6.0",
43
+ "@typescript-eslint/parser": "^8.6.0",
44
+ "eslint": "^9.10.0",
45
+ "eslint-config-prettier": "^9.1.0",
46
+ "prettier": "^3.3.3",
47
+ "typescript": "^5.6.2"
48
+ },
49
+ "dependencies": {
50
+ "typescript-eslint": "^8.42.0"
51
+ }
52
+ }