launch-unity 0.7.0 → 0.9.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 CHANGED
@@ -15,27 +15,35 @@ npm install -g launch-unity
15
15
  npx launch-unity
16
16
  ```
17
17
 
18
- ## 使用例
18
+ ## 使い方
19
19
  ```bash
20
- # NPX(推奨 / インストール不要)
21
- npx launch-unity # カレントディレクトリから3階層下までUnityプロジェクトを探索して開く
22
- npx launch-unity /path/to/Proj # プロジェクトを指定
23
- npx launch-unity /path Android # ビルドターゲットを指定
24
- npx -y launch-unity # npx の「Ok to proceed?」確認をスキップ
25
-
26
- # `--` 以降は Unity のコマンドライン引数をそのまま転送
27
- npx launch-unity . -- -batchmode -quit -nographics -logFile -
20
+ # 構文
21
+ launch-unity [OPTIONS] [PROJECT_PATH] [PLATFORM] [-- UNITY_ARGS...]
22
+
23
+ # 引数
24
+ # PROJECT_PATH Unityプロジェクトのディレクトリ(省略時は3階層下まで探索)
25
+ # PLATFORM Unityの -buildTarget に渡す値(例: StandaloneOSX, Android, iOS)
26
+
27
+ # オプション
28
+ # -h, --help ヘルプを表示
29
+ # -r, --restart Unityを再起動
30
+ # -a, -u, --add-unity-hub, --unity-hub-entry
31
+ # Unity Hub に登録(Unityは起動しない)
32
+ # -f, --favorite Unity Hub にお気に入りとして登録(Unityは起動しない)
33
+
34
+ # 例
35
+ npx launch-unity # プロジェクトを探索して開く
36
+ npx launch-unity /path/to/Proj # 指定プロジェクトを開く
37
+ npx launch-unity /path Android # ビルドターゲットを指定
38
+ npx launch-unity -r # Unityを再起動
39
+ npx launch-unity -a # Unity Hub に登録のみ(Unityは起動しない)
40
+ npx launch-unity -f # Unity Hub にお気に入り登録(Unityは起動しない)
41
+ npx launch-unity . -- -batchmode -quit -nographics -logFile - # Unity引数を渡す
28
42
  npx launch-unity /path Android -- -executeMethod My.Build.Entry
29
-
30
- # グローバルインストール後
31
- launch-unity
32
- launch-unity /path/to/MyUnityProject Android
33
- launch-unity . -- -buildTarget iOS -projectPath . # 上書きも可能
34
43
  ```
35
44
 
36
45
  指定した Unity プロジェクトの `ProjectSettings/ProjectVersion.txt` から必要な Unity Editor のバージョンを読み取り、
37
- Unity Hub でインストール済みの該当バージョンを起動する macOS/Windows 向け TypeScript 製 CLI です。`Temp/UnityLockfile` が
38
- 存在する場合は、削除して続行するかをターミナル上で確認します。
46
+ Unity Hub でインストール済みの該当バージョンを起動する macOS/Windows 向け TypeScript 製 CLI です。
39
47
 
40
48
  既定で想定する Unity のパス:
41
49
  - macOS: `/Applications/Unity/Hub/Editor/<version>/Unity.app/Contents/MacOS/Unity`
package/README.md CHANGED
@@ -15,28 +15,36 @@ npm install -g launch-unity
15
15
  npx launch-unity
16
16
  ```
17
17
 
18
- ## Usage Examples
18
+ ## Usage
19
19
  ```bash
20
- # NPX (recommended, zero install)
21
- npx launch-unity # Search up to 3 levels deep for a Unity project and open it
22
- npx launch-unity /path/to/Proj # Open a specific project
23
- npx launch-unity /path Android # Specify build target
24
- npx -y launch-unity # Skip npx "Ok to proceed?" prompt
20
+ # Syntax
21
+ launch-unity [OPTIONS] [PROJECT_PATH] [PLATFORM] [-- UNITY_ARGS...]
25
22
 
26
- # Pass through Unity CLI args with --
27
- npx launch-unity . -- -batchmode -quit -nographics -logFile -
23
+ # Arguments
24
+ # PROJECT_PATH Unity project directory (searches up to 3 levels deep if omitted)
25
+ # PLATFORM Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
26
+
27
+ # Options
28
+ # -h, --help Show help
29
+ # -r, --restart Kill running Unity and restart
30
+ # -a, -u, --add-unity-hub, --unity-hub-entry
31
+ # Register to Unity Hub (does not launch Unity)
32
+ # -f, --favorite Register to Unity Hub as favorite (does not launch Unity)
33
+
34
+ # Examples
35
+ npx launch-unity # Search for project and open
36
+ npx launch-unity /path/to/Proj # Open specific project
37
+ npx launch-unity /path Android # Specify build target
38
+ npx launch-unity -r # Restart Unity
39
+ npx launch-unity -a # Register to Unity Hub only (does not launch Unity)
40
+ npx launch-unity -f # Register as favorite (does not launch Unity)
41
+ npx launch-unity . -- -batchmode -quit -nographics -logFile - # Pass Unity args
28
42
  npx launch-unity /path Android -- -executeMethod My.Build.Entry
29
-
30
- # Installed globally
31
- launch-unity
32
- launch-unity /path/to/MyUnityProject Android
33
- launch-unity . -- -buildTarget iOS -projectPath . # You can override
34
43
  ```
35
44
 
36
45
  A TypeScript CLI for macOS and Windows that reads the required Unity Editor version from
37
46
  `ProjectSettings/ProjectVersion.txt`, launches the matching Unity installed via Unity Hub,
38
- and opens the project. If `Temp/UnityLockfile` exists, it asks in the terminal whether to
39
- delete it before continuing.
47
+ and opens the project.
40
48
 
41
49
  Default Unity paths assumed:
42
50
  - macOS: `/Applications/Unity/Hub/Editor/<version>/Unity.app/Contents/MacOS/Unity`
@@ -47,20 +55,6 @@ Default Unity paths assumed:
47
55
  - `C:\\Program Files\\Unity\\Hub\\Editor\\<version>\\Editor\\Unity.exe`
48
56
 
49
57
 
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
58
  ## Troubleshooting
65
59
  - Error: `ProjectVersion.txt not found`
66
60
  - The provided directory is not a Unity project. Point to the project root.
package/dist/launch.js CHANGED
@@ -6,9 +6,10 @@
6
6
  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
- import { join, resolve } from "node:path";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
10
11
  import { promisify } from "node:util";
11
- import { updateLastModifiedIfExists } from "./unityHub.js";
12
+ import { ensureProjectEntryAndUpdate, updateLastModifiedIfExists } from "./unityHub.js";
12
13
  const execFileAsync = promisify(execFile);
13
14
  const UNITY_EXECUTABLE_PATTERN_MAC = /Unity\.app\/Contents\/MacOS\/Unity/i;
14
15
  const UNITY_EXECUTABLE_PATTERN_WINDOWS = /Unity\.exe/i;
@@ -26,16 +27,33 @@ function parseArgs(argv) {
26
27
  const positionals = [];
27
28
  let maxDepth = 3; // default 3; -1 means unlimited
28
29
  let restart = false;
30
+ let addUnityHub = false;
31
+ let favoriteUnityHub = false;
29
32
  for (let i = 0; i < cliArgs.length; i++) {
30
33
  const arg = cliArgs[i] ?? "";
31
34
  if (arg === "--help" || arg === "-h") {
32
35
  printHelp();
33
36
  process.exit(0);
34
37
  }
38
+ if (arg === "--version" || arg === "-v") {
39
+ console.log(getVersion());
40
+ process.exit(0);
41
+ }
35
42
  if (arg === "-r" || arg === "--restart") {
36
43
  restart = true;
37
44
  continue;
38
45
  }
46
+ if (arg === "-u" ||
47
+ arg === "-a" ||
48
+ arg === "--unity-hub-entry" ||
49
+ arg === "--add-unity-hub") {
50
+ addUnityHub = true;
51
+ continue;
52
+ }
53
+ if (arg === "-f" || arg === "--favorite") {
54
+ favoriteUnityHub = true;
55
+ continue;
56
+ }
39
57
  if (arg.startsWith("--max-depth")) {
40
58
  const parts = arg.split("=");
41
59
  if (parts.length === 2) {
@@ -84,7 +102,7 @@ function parseArgs(argv) {
84
102
  projectPath = resolve(positionals[0] ?? "");
85
103
  platform = String(positionals[1] ?? "");
86
104
  }
87
- const options = { unityArgs, searchMaxDepth: maxDepth, restart };
105
+ const options = { unityArgs, searchMaxDepth: maxDepth, restart, addUnityHub, favoriteUnityHub };
88
106
  if (projectPath !== undefined) {
89
107
  options.projectPath = projectPath;
90
108
  }
@@ -93,6 +111,13 @@ function parseArgs(argv) {
93
111
  }
94
112
  return options;
95
113
  }
114
+ function getVersion() {
115
+ const currentFilePath = fileURLToPath(import.meta.url);
116
+ const currentDir = dirname(currentFilePath);
117
+ const packageJsonPath = join(currentDir, "..", "package.json");
118
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
119
+ return packageJson.version;
120
+ }
96
121
  function printHelp() {
97
122
  const help = `
98
123
  Usage: launch-unity [PROJECT_PATH] [PLATFORM] -- [UNITY_ARGS...]
@@ -109,8 +134,12 @@ Forwarding:
109
134
 
110
135
  Flags:
111
136
  -h, --help Show this help message
137
+ -v, --version Show version number
112
138
  -r, --restart Kill running Unity and restart
113
139
  --max-depth <N> Search depth when PROJECT_PATH is omitted (default 3, -1 unlimited)
140
+ -u, -a, --unity-hub-entry, --add-unity-hub
141
+ Add to Unity Hub if missing and update lastModified (does not launch Unity)
142
+ -f, --favorite Add to Unity Hub as favorite (does not launch Unity)
114
143
  `;
115
144
  process.stdout.write(help);
116
145
  }
@@ -538,8 +567,7 @@ function findUnityProjectBfs(rootDir, maxDepth) {
538
567
  return undefined;
539
568
  }
540
569
  function launch(opts) {
541
- const { projectPath, platform, unityArgs } = opts;
542
- const unityVersion = getUnityVersion(projectPath);
570
+ const { projectPath, platform, unityArgs, unityVersion } = opts;
543
571
  const unityPath = getUnityPath(unityVersion);
544
572
  console.log(`Detected Unity version: ${unityVersion}`);
545
573
  if (!existsSync(unityPath)) {
@@ -577,6 +605,23 @@ async function main() {
577
605
  resolvedProjectPath = found;
578
606
  }
579
607
  ensureProjectPath(resolvedProjectPath);
608
+ const unityVersion = getUnityVersion(resolvedProjectPath);
609
+ // Unity Hub only mode: -a or -f flags skip launching Unity
610
+ const unityHubOnlyMode = options.addUnityHub || options.favoriteUnityHub;
611
+ if (unityHubOnlyMode) {
612
+ console.log(`Detected Unity version: ${unityVersion}`);
613
+ console.log(`Project Path: ${resolvedProjectPath}`);
614
+ const now = new Date();
615
+ try {
616
+ await ensureProjectEntryAndUpdate(resolvedProjectPath, unityVersion, now, options.favoriteUnityHub);
617
+ console.log("Unity Hub entry updated.");
618
+ }
619
+ catch (error) {
620
+ const message = error instanceof Error ? error.message : String(error);
621
+ console.warn(`Failed to update Unity Hub: ${message}`);
622
+ }
623
+ return;
624
+ }
580
625
  if (options.restart) {
581
626
  await killRunningUnity(resolvedProjectPath);
582
627
  }
@@ -594,11 +639,13 @@ async function main() {
594
639
  projectPath: resolvedProjectPath,
595
640
  platform: options.platform,
596
641
  unityArgs: options.unityArgs,
642
+ unityVersion,
597
643
  };
598
644
  launch(resolved);
599
645
  // Best-effort update of Unity Hub's lastModified timestamp.
646
+ const now = new Date();
600
647
  try {
601
- await updateLastModifiedIfExists(resolvedProjectPath, new Date());
648
+ await updateLastModifiedIfExists(resolvedProjectPath, now);
602
649
  }
603
650
  catch (error) {
604
651
  const message = error instanceof Error ? error.message : String(error);
package/dist/unityHub.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
- import { join, resolve } from "node:path";
2
+ import { realpathSync } from "node:fs";
3
+ import { basename, dirname, join, resolve } from "node:path";
3
4
  const resolveUnityHubProjectFiles = () => {
4
5
  if (process.platform === "darwin") {
5
6
  const home = process.env.HOME;
@@ -30,12 +31,91 @@ const normalizePath = (target) => {
30
31
  const resolvedPath = resolve(target);
31
32
  return removeTrailingSeparators(resolvedPath);
32
33
  };
34
+ const resolvePathWithActualCase = (target) => {
35
+ try {
36
+ return removeTrailingSeparators(realpathSync.native(target));
37
+ }
38
+ catch {
39
+ return normalizePath(target);
40
+ }
41
+ };
33
42
  const toComparablePath = (value) => {
34
43
  return value.replace(/\\/g, "/").toLocaleLowerCase();
35
44
  };
36
45
  const pathsEqual = (left, right) => {
37
46
  return toComparablePath(normalizePath(left)) === toComparablePath(normalizePath(right));
38
47
  };
48
+ const safeParseProjectsJson = (content) => {
49
+ try {
50
+ return JSON.parse(content);
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ };
56
+ const logDebug = (message) => {
57
+ if (process.env["LAUNCH_UNITY_DEBUG"] === "1") {
58
+ console.log(`[unityHub] ${message}`);
59
+ }
60
+ };
61
+ export const ensureProjectEntryAndUpdate = async (projectPath, version, when, setFavorite = false) => {
62
+ const canonicalProjectPath = resolvePathWithActualCase(projectPath);
63
+ const projectTitle = basename(canonicalProjectPath);
64
+ const containingFolderPath = dirname(canonicalProjectPath);
65
+ const candidates = resolveUnityHubProjectFiles();
66
+ if (candidates.length === 0) {
67
+ logDebug("No Unity Hub project files found.");
68
+ return;
69
+ }
70
+ for (const path of candidates) {
71
+ logDebug(`Trying Unity Hub file: ${path}`);
72
+ const content = await readFile(path, "utf8").catch(() => undefined);
73
+ if (!content) {
74
+ logDebug("Read failed or empty content, skipping.");
75
+ continue;
76
+ }
77
+ const json = safeParseProjectsJson(content);
78
+ if (!json) {
79
+ logDebug("Parse failed, skipping.");
80
+ continue;
81
+ }
82
+ const data = { ...(json.data ?? {}) };
83
+ const existingKey = Object.keys(data).find((key) => {
84
+ const entryPath = data[key]?.path;
85
+ return entryPath ? pathsEqual(entryPath, projectPath) : false;
86
+ });
87
+ const targetKey = existingKey ?? canonicalProjectPath;
88
+ const existingEntry = existingKey ? data[existingKey] : undefined;
89
+ logDebug(existingKey
90
+ ? `Found existing entry for project (key=${existingKey}). Updating lastModified.`
91
+ : `No existing entry. Adding new entry (key=${targetKey}).`);
92
+ const updatedEntry = {
93
+ ...existingEntry,
94
+ path: existingEntry?.path ?? canonicalProjectPath,
95
+ containingFolderPath: existingEntry?.containingFolderPath ?? containingFolderPath,
96
+ version: existingEntry?.version ?? version,
97
+ title: existingEntry?.title ?? projectTitle,
98
+ lastModified: when.getTime(),
99
+ isFavorite: setFavorite ? true : (existingEntry?.isFavorite ?? false),
100
+ };
101
+ const updatedJson = {
102
+ ...json,
103
+ data: {
104
+ ...data,
105
+ [targetKey]: updatedEntry,
106
+ },
107
+ };
108
+ try {
109
+ await writeFile(path, JSON.stringify(updatedJson, undefined, 2), "utf8");
110
+ logDebug("Write succeeded.");
111
+ }
112
+ catch (error) {
113
+ logDebug(`Write failed: ${error instanceof Error ? error.message : String(error)}`);
114
+ // Ignore write errors to avoid breaking CLI flow
115
+ }
116
+ return;
117
+ }
118
+ };
39
119
  export const updateLastModifiedIfExists = async (projectPath, when) => {
40
120
  const candidates = resolveUnityHubProjectFiles();
41
121
  if (candidates.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launch-unity",
3
- "version": "0.7.0",
3
+ "version": "0.9.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",
@@ -39,15 +39,15 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "24.10.1",
42
- "@typescript-eslint/eslint-plugin": "8.48.0",
43
- "@typescript-eslint/parser": "8.48.0",
42
+ "@typescript-eslint/eslint-plugin": "8.48.1",
43
+ "@typescript-eslint/parser": "8.48.1",
44
44
  "eslint": "9.39.1",
45
45
  "eslint-config-prettier": "10.1.8",
46
- "prettier": "3.7.3",
46
+ "prettier": "3.7.4",
47
47
  "typescript": "5.9.3"
48
48
  },
49
49
  "dependencies": {
50
- "typescript-eslint": "8.48.0"
50
+ "typescript-eslint": "8.48.1"
51
51
  },
52
52
  "overrides": {
53
53
  "js-yaml": "4.1.1"