launch-unity 0.8.0 → 0.10.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
@@ -19,6 +19,7 @@ npx launch-unity
19
19
  ```bash
20
20
  # 構文
21
21
  launch-unity [OPTIONS] [PROJECT_PATH] [PLATFORM] [-- UNITY_ARGS...]
22
+ launch-unity update
22
23
 
23
24
  # 引数
24
25
  # PROJECT_PATH Unityプロジェクトのディレクトリ(省略時は3階層下まで探索)
@@ -40,6 +41,12 @@ npx launch-unity -a # Unity Hub に登録のみ(Unityは起動
40
41
  npx launch-unity -f # Unity Hub にお気に入り登録(Unityは起動しない)
41
42
  npx launch-unity . -- -batchmode -quit -nographics -logFile - # Unity引数を渡す
42
43
  npx launch-unity /path Android -- -executeMethod My.Build.Entry
44
+
45
+ # 自己更新(npmグローバルインストール向け)
46
+ launch-unity update
47
+
48
+ # `update` という名前のディレクトリを開きたい場合は明示する
49
+ launch-unity ./update
43
50
  ```
44
51
 
45
52
  指定した Unity プロジェクトの `ProjectSettings/ProjectVersion.txt` から必要な Unity Editor のバージョンを読み取り、
package/README.md CHANGED
@@ -19,6 +19,7 @@ npx launch-unity
19
19
  ```bash
20
20
  # Syntax
21
21
  launch-unity [OPTIONS] [PROJECT_PATH] [PLATFORM] [-- UNITY_ARGS...]
22
+ launch-unity update
22
23
 
23
24
  # Arguments
24
25
  # PROJECT_PATH Unity project directory (searches up to 3 levels deep if omitted)
@@ -40,6 +41,12 @@ npx launch-unity -a # Register to Unity Hub only (does not launch
40
41
  npx launch-unity -f # Register as favorite (does not launch Unity)
41
42
  npx launch-unity . -- -batchmode -quit -nographics -logFile - # Pass Unity args
42
43
  npx launch-unity /path Android -- -executeMethod My.Build.Entry
44
+
45
+ # Self update (for npm global install)
46
+ launch-unity update
47
+
48
+ # If you have a project directory named "update", specify it explicitly
49
+ launch-unity ./update
43
50
  ```
44
51
 
45
52
  A TypeScript CLI for macOS and Windows that reads the required Unity Editor version from
package/dist/launch.js CHANGED
@@ -6,7 +6,8 @@
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
12
  import { ensureProjectEntryAndUpdate, updateLastModifiedIfExists } from "./unityHub.js";
12
13
  const execFileAsync = promisify(execFile);
@@ -18,11 +19,128 @@ const PROCESS_LIST_ARGS_MAC = ["-axo", "pid=,command=", "-ww"];
18
19
  const WINDOWS_POWERSHELL = "powershell";
19
20
  const UNITY_LOCKFILE_NAME = "UnityLockfile";
20
21
  const TEMP_DIRECTORY_NAME = "Temp";
22
+ const npmExecutableName = () => {
23
+ return process.platform === "win32" ? "npm.cmd" : "npm";
24
+ };
25
+ const runCommand = async (command, args) => {
26
+ return await new Promise((resolve) => {
27
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
28
+ let stdout = "";
29
+ let stderr = "";
30
+ child.stdout?.on("data", (chunk) => {
31
+ stdout += chunk.toString("utf8");
32
+ });
33
+ child.stderr?.on("data", (chunk) => {
34
+ stderr += chunk.toString("utf8");
35
+ });
36
+ child.on("error", (error) => {
37
+ resolve({ exitCode: 127, stdout, stderr: `${stderr}${error.message}\n` });
38
+ });
39
+ child.on("close", (code) => {
40
+ resolve({ exitCode: code ?? 1, stdout, stderr });
41
+ });
42
+ });
43
+ };
44
+ const parseSemverTriplet = (value) => {
45
+ const normalized = value.trim();
46
+ const withoutBuild = normalized.split("+")[0] ?? normalized;
47
+ const match = withoutBuild.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
48
+ if (!match) {
49
+ return undefined;
50
+ }
51
+ const major = Number.parseInt(match[1] ?? "", 10);
52
+ const minor = Number.parseInt(match[2] ?? "", 10);
53
+ const patch = Number.parseInt(match[3] ?? "", 10);
54
+ if (!Number.isFinite(major) || !Number.isFinite(minor) || !Number.isFinite(patch)) {
55
+ return undefined;
56
+ }
57
+ const prereleaseRaw = match[4] ?? undefined;
58
+ if (!prereleaseRaw) {
59
+ return { major, minor, patch };
60
+ }
61
+ const prereleaseIdentifiers = prereleaseRaw
62
+ .split(".")
63
+ .map((part) => part.trim())
64
+ .filter((part) => part.length > 0)
65
+ .map((part) => {
66
+ const numeric = part.match(/^\d+$/);
67
+ if (numeric) {
68
+ const value = Number.parseInt(part, 10);
69
+ if (Number.isFinite(value)) {
70
+ return value;
71
+ }
72
+ }
73
+ return part;
74
+ });
75
+ if (prereleaseIdentifiers.length === 0) {
76
+ return { major, minor, patch };
77
+ }
78
+ return { major, minor, patch, prereleaseIdentifiers };
79
+ };
80
+ const compareSemverTriplet = (left, right) => {
81
+ if (left.major !== right.major) {
82
+ return left.major < right.major ? -1 : 1;
83
+ }
84
+ if (left.minor !== right.minor) {
85
+ return left.minor < right.minor ? -1 : 1;
86
+ }
87
+ if (left.patch !== right.patch) {
88
+ return left.patch < right.patch ? -1 : 1;
89
+ }
90
+ const leftPre = left.prereleaseIdentifiers;
91
+ const rightPre = right.prereleaseIdentifiers;
92
+ if (!leftPre && !rightPre) {
93
+ return 0;
94
+ }
95
+ if (!leftPre && rightPre) {
96
+ return 1;
97
+ }
98
+ if (leftPre && !rightPre) {
99
+ return -1;
100
+ }
101
+ const leftIdentifiers = leftPre ?? [];
102
+ const rightIdentifiers = rightPre ?? [];
103
+ const length = Math.max(leftIdentifiers.length, rightIdentifiers.length);
104
+ for (let i = 0; i < length; i++) {
105
+ const leftId = leftIdentifiers[i];
106
+ const rightId = rightIdentifiers[i];
107
+ if (leftId === undefined && rightId === undefined) {
108
+ return 0;
109
+ }
110
+ if (leftId === undefined) {
111
+ return -1;
112
+ }
113
+ if (rightId === undefined) {
114
+ return 1;
115
+ }
116
+ if (leftId === rightId) {
117
+ continue;
118
+ }
119
+ const leftIsNumber = typeof leftId === "number";
120
+ const rightIsNumber = typeof rightId === "number";
121
+ if (leftIsNumber && rightIsNumber) {
122
+ return leftId < rightId ? -1 : 1;
123
+ }
124
+ if (leftIsNumber !== rightIsNumber) {
125
+ return leftIsNumber ? -1 : 1;
126
+ }
127
+ const leftText = String(leftId);
128
+ const rightText = String(rightId);
129
+ return leftText < rightText ? -1 : 1;
130
+ }
131
+ return 0;
132
+ };
21
133
  function parseArgs(argv) {
22
134
  const args = argv.slice(2);
23
135
  const doubleDashIndex = args.indexOf("--");
24
- const cliArgs = doubleDashIndex >= 0 ? args.slice(0, doubleDashIndex) : args;
136
+ let cliArgs = doubleDashIndex >= 0 ? args.slice(0, doubleDashIndex) : args;
25
137
  const unityArgs = doubleDashIndex >= 0 ? args.slice(doubleDashIndex + 1) : [];
138
+ let subcommand;
139
+ const firstToken = cliArgs[0];
140
+ if (firstToken === "update") {
141
+ subcommand = "update";
142
+ cliArgs = cliArgs.slice(1);
143
+ }
26
144
  const positionals = [];
27
145
  let maxDepth = 3; // default 3; -1 means unlimited
28
146
  let restart = false;
@@ -34,6 +152,10 @@ function parseArgs(argv) {
34
152
  printHelp();
35
153
  process.exit(0);
36
154
  }
155
+ if (arg === "--version" || arg === "-v") {
156
+ console.log(getVersion());
157
+ process.exit(0);
158
+ }
37
159
  if (arg === "-r" || arg === "--restart") {
38
160
  restart = true;
39
161
  continue;
@@ -97,7 +219,16 @@ function parseArgs(argv) {
97
219
  projectPath = resolve(positionals[0] ?? "");
98
220
  platform = String(positionals[1] ?? "");
99
221
  }
100
- const options = { unityArgs, searchMaxDepth: maxDepth, restart, addUnityHub, favoriteUnityHub };
222
+ const options = {
223
+ unityArgs,
224
+ searchMaxDepth: maxDepth,
225
+ restart,
226
+ addUnityHub,
227
+ favoriteUnityHub,
228
+ };
229
+ if (subcommand) {
230
+ options.subcommand = subcommand;
231
+ }
101
232
  if (projectPath !== undefined) {
102
233
  options.projectPath = projectPath;
103
234
  }
@@ -106,14 +237,23 @@ function parseArgs(argv) {
106
237
  }
107
238
  return options;
108
239
  }
240
+ function getVersion() {
241
+ const currentFilePath = fileURLToPath(import.meta.url);
242
+ const currentDir = dirname(currentFilePath);
243
+ const packageJsonPath = join(currentDir, "..", "package.json");
244
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
245
+ return packageJson.version;
246
+ }
109
247
  function printHelp() {
110
248
  const help = `
111
- Usage: launch-unity [PROJECT_PATH] [PLATFORM] -- [UNITY_ARGS...]
249
+ Usage:
250
+ launch-unity [PROJECT_PATH] [PLATFORM] -- [UNITY_ARGS...]
251
+ launch-unity update
112
252
 
113
253
  Open a Unity project with the matching Unity Editor version installed by Unity Hub.
114
254
 
115
255
  Arguments:
116
- PROJECT_PATH Optional. Defaults to current directory
256
+ PROJECT_PATH Optional. If omitted, searches under the current directory (see --max-depth)
117
257
  PLATFORM Optional. Passed to Unity as -buildTarget (e.g., StandaloneOSX, Android, iOS)
118
258
 
119
259
  Forwarding:
@@ -122,11 +262,15 @@ Forwarding:
122
262
 
123
263
  Flags:
124
264
  -h, --help Show this help message
265
+ -v, --version Show version number
125
266
  -r, --restart Kill running Unity and restart
126
267
  --max-depth <N> Search depth when PROJECT_PATH is omitted (default 3, -1 unlimited)
127
268
  -u, -a, --unity-hub-entry, --add-unity-hub
128
269
  Add to Unity Hub if missing and update lastModified (does not launch Unity)
129
270
  -f, --favorite Add to Unity Hub as favorite (does not launch Unity)
271
+
272
+ Commands:
273
+ update Update launch-unity to the latest version (npm global install)
130
274
  `;
131
275
  process.stdout.write(help);
132
276
  }
@@ -577,6 +721,10 @@ function launch(opts) {
577
721
  }
578
722
  async function main() {
579
723
  const options = parseArgs(process.argv);
724
+ if (options.subcommand === "update") {
725
+ await runSelfUpdate();
726
+ return;
727
+ }
580
728
  let resolvedProjectPath = options.projectPath;
581
729
  if (!resolvedProjectPath) {
582
730
  const searchRoot = process.cwd();
@@ -639,6 +787,59 @@ async function main() {
639
787
  console.warn(`Failed to update Unity Hub lastModified: ${message}`);
640
788
  }
641
789
  }
790
+ async function runSelfUpdate() {
791
+ const currentVersion = getVersion();
792
+ const npmCmd = npmExecutableName();
793
+ const latestResult = await runCommand(npmCmd, ["view", "launch-unity", "version"]);
794
+ if (latestResult.exitCode !== 0) {
795
+ console.error("Error: Failed to retrieve latest version from npm.");
796
+ if (latestResult.stderr.trim().length > 0) {
797
+ process.stderr.write(latestResult.stderr);
798
+ }
799
+ process.exit(1);
800
+ return;
801
+ }
802
+ const latestVersion = latestResult.stdout.trim();
803
+ if (latestVersion.length === 0) {
804
+ console.error("Error: npm returned an empty version.");
805
+ process.exit(1);
806
+ return;
807
+ }
808
+ console.log(`Current: ${currentVersion}`);
809
+ console.log(`Latest: ${latestVersion}`);
810
+ const currentSemver = parseSemverTriplet(currentVersion);
811
+ const latestSemver = parseSemverTriplet(latestVersion);
812
+ if (currentSemver && latestSemver) {
813
+ const cmp = compareSemverTriplet(currentSemver, latestSemver);
814
+ if (cmp >= 0) {
815
+ console.log("Already up to date.");
816
+ return;
817
+ }
818
+ }
819
+ else {
820
+ if (currentVersion === latestVersion) {
821
+ console.log("Already up to date.");
822
+ return;
823
+ }
824
+ }
825
+ const installArgs = ["install", "-g", `launch-unity@${latestVersion}`, "--ignore-scripts"];
826
+ console.log(`Running: ${npmCmd} ${installArgs.join(" ")}`);
827
+ const installResult = await runCommand(npmCmd, installArgs);
828
+ if (installResult.exitCode === 0) {
829
+ console.log("Update complete.");
830
+ return;
831
+ }
832
+ console.error(`Error: Update failed (exit code ${installResult.exitCode}).`);
833
+ if (installResult.stderr.trim().length > 0) {
834
+ process.stderr.write(installResult.stderr);
835
+ }
836
+ if (process.platform === "darwin") {
837
+ const sudoSuggestion = `sudo ${npmCmd} ${installArgs.join(" ")}`;
838
+ console.error("If this is a permissions issue, try:");
839
+ console.error(` ${sudoSuggestion}`);
840
+ }
841
+ process.exit(1);
842
+ }
642
843
  main().catch((error) => {
643
844
  console.error(error);
644
845
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launch-unity",
3
- "version": "0.8.0",
3
+ "version": "0.10.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",