@rage-against-the-pixel/unity-cli 3.0.2 → 4.0.1
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.md +86 -224
- package/dist/android-sdk.js +8 -1
- package/dist/android-sdk.js.map +1 -1
- package/dist/cli.js +180 -505
- package/dist/cli.js.map +1 -1
- package/dist/failure-markers.d.ts +17 -0
- package/dist/failure-markers.js +46 -0
- package/dist/failure-markers.js.map +1 -0
- package/dist/github-actions-ci.d.ts +1 -0
- package/dist/github-actions-ci.js +17 -3
- package/dist/github-actions-ci.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/license-client.d.ts +119 -2
- package/dist/license-client.js +737 -56
- package/dist/license-client.js.map +1 -1
- package/dist/logger-provider.d.ts +2 -0
- package/dist/logger-provider.js +3 -0
- package/dist/logger-provider.js.map +1 -1
- package/dist/logging.d.ts +5 -0
- package/dist/logging.js +47 -0
- package/dist/logging.js.map +1 -1
- package/dist/process-tracker.d.ts +71 -0
- package/dist/process-tracker.js +213 -0
- package/dist/process-tracker.js.map +1 -0
- package/dist/unity-cli.d.ts +97 -0
- package/dist/unity-cli.js +591 -0
- package/dist/unity-cli.js.map +1 -0
- package/dist/unity-editor.d.ts +25 -16
- package/dist/unity-editor.js +204 -107
- package/dist/unity-editor.js.map +1 -1
- package/dist/unity-installer.d.ts +139 -0
- package/dist/unity-installer.js +1018 -0
- package/dist/unity-installer.js.map +1 -0
- package/dist/unity-logging.d.ts +16 -1
- package/dist/unity-logging.js +55 -3
- package/dist/unity-logging.js.map +1 -1
- package/dist/unity-project.d.ts +15 -0
- package/dist/unity-project.js +80 -0
- package/dist/unity-project.js.map +1 -1
- package/dist/upm-cli.d.ts +8 -8
- package/dist/upm-cli.js +8 -8
- package/dist/upm-cli.js.map +1 -1
- package/dist/utilities.d.ts +27 -7
- package/dist/utilities.js +119 -63
- package/dist/utilities.js.map +1 -1
- package/package.json +10 -11
- package/dist/unity-hub.d.ts +0 -140
- package/dist/unity-hub.js +0 -1425
- package/dist/unity-hub.js.map +0 -1
package/dist/unity-hub.js
DELETED
|
@@ -1,1425 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.UnityHub = exports.LINUX_HUB_EXECUTABLE_LEGACY = exports.LINUX_HUB_EXECUTABLE_MODERN = void 0;
|
|
37
|
-
exports.resolveLinuxHubExecutable = resolveLinuxHubExecutable;
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
|
-
const os = __importStar(require("os"));
|
|
40
|
-
const path = __importStar(require("path"));
|
|
41
|
-
const yaml = __importStar(require("yaml"));
|
|
42
|
-
const asar = __importStar(require("@electron/asar"));
|
|
43
|
-
const child_process_1 = require("child_process");
|
|
44
|
-
const logging_1 = require("./logging");
|
|
45
|
-
const unity_editor_1 = require("./unity-editor");
|
|
46
|
-
const unity_version_1 = require("./unity-version");
|
|
47
|
-
const semver_1 = require("semver");
|
|
48
|
-
const utilities_1 = require("./utilities");
|
|
49
|
-
const unity_releases_api_1 = require("@rage-against-the-pixel/unity-releases-api");
|
|
50
|
-
/** First Unity Hub line with native Windows ARM64 installers on the public CDN. */
|
|
51
|
-
const MIN_NATIVE_WINDOWS_ARM64_HUB_VERSION = (0, semver_1.coerce)('3.17.0');
|
|
52
|
-
/** Allowed characters in a Debian package version (no shell metacharacters). */
|
|
53
|
-
const LINUX_HUB_DEB_VERSION_RE = /^[0-9A-Za-z.+~:-]+$/;
|
|
54
|
-
/** Hub 3.20+ Electron Forge deb layout. */
|
|
55
|
-
exports.LINUX_HUB_EXECUTABLE_MODERN = '/usr/lib/unityhub/unityhub';
|
|
56
|
-
/** Hub ≤3.19 fpm / electron-builder layout. */
|
|
57
|
-
exports.LINUX_HUB_EXECUTABLE_LEGACY = '/opt/unityhub/unityhub';
|
|
58
|
-
/**
|
|
59
|
-
* Resolves the Unity Hub binary on Linux.
|
|
60
|
-
* Prefers UNITY_HUB_PATH, then the Hub 3.20+ path, then the legacy /opt path.
|
|
61
|
-
* When neither is present (pre-install), defaults to the modern path.
|
|
62
|
-
*/
|
|
63
|
-
function resolveLinuxHubExecutable(envPath = process.env.UNITY_HUB_PATH, existsSync = fs.existsSync) {
|
|
64
|
-
if (envPath !== undefined && envPath.length > 0) {
|
|
65
|
-
return envPath;
|
|
66
|
-
}
|
|
67
|
-
if (existsSync(exports.LINUX_HUB_EXECUTABLE_MODERN)) {
|
|
68
|
-
return exports.LINUX_HUB_EXECUTABLE_MODERN;
|
|
69
|
-
}
|
|
70
|
-
if (existsSync(exports.LINUX_HUB_EXECUTABLE_LEGACY)) {
|
|
71
|
-
return exports.LINUX_HUB_EXECUTABLE_LEGACY;
|
|
72
|
-
}
|
|
73
|
-
return exports.LINUX_HUB_EXECUTABLE_MODERN;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Fixed bootstrap for Linux Hub apt repo + update index. No user-controlled interpolation (CodeQL).
|
|
77
|
-
* Uses DEB822 .sources (Hub 3.20+) and removes legacy .list to avoid duplicate-source warnings.
|
|
78
|
-
*/
|
|
79
|
-
const LINUX_HUB_LINUX_UPDATE_REPO_BOOTSTRAP = `#!/bin/sh
|
|
80
|
-
set -e
|
|
81
|
-
wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | sudo tee /usr/share/keyrings/Unity_Technologies_ApS.gpg >/dev/null
|
|
82
|
-
sudo rm -f /etc/apt/sources.list.d/unityhub.list
|
|
83
|
-
sudo tee /etc/apt/sources.list.d/unityhub.sources >/dev/null <<'EOF'
|
|
84
|
-
Types: deb
|
|
85
|
-
URIs: https://hub.unity3d.com/linux/repos/deb
|
|
86
|
-
Suites: stable
|
|
87
|
-
Components: main
|
|
88
|
-
Signed-By: /usr/share/keyrings/Unity_Technologies_ApS.gpg
|
|
89
|
-
EOF
|
|
90
|
-
sudo apt-get update --allow-releaseinfo-change
|
|
91
|
-
`;
|
|
92
|
-
/**
|
|
93
|
-
* First phase of fresh Linux Hub install: machine-id, repo keys, jammy mirror, apt-get update.
|
|
94
|
-
* No user-controlled interpolation. Uses DEB822 .sources (Hub 3.20+).
|
|
95
|
-
*/
|
|
96
|
-
const LINUX_HUB_LINUX_INSTALL_BOOTSTRAP = `#!/bin/sh
|
|
97
|
-
set -e
|
|
98
|
-
dbus-uuidgen >/etc/machine-id && mkdir -p /var/lib/dbus/ && ln -sf /etc/machine-id /var/lib/dbus/machine-id
|
|
99
|
-
wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | tee /usr/share/keyrings/Unity_Technologies_ApS.gpg >/dev/null
|
|
100
|
-
rm -f /etc/apt/sources.list.d/unityhub.list
|
|
101
|
-
tee /etc/apt/sources.list.d/unityhub.sources >/dev/null <<'EOF'
|
|
102
|
-
Types: deb
|
|
103
|
-
URIs: https://hub.unity3d.com/linux/repos/deb
|
|
104
|
-
Suites: stable
|
|
105
|
-
Components: main
|
|
106
|
-
Signed-By: /usr/share/keyrings/Unity_Technologies_ApS.gpg
|
|
107
|
-
EOF
|
|
108
|
-
echo "deb https://archive.ubuntu.com/ubuntu jammy main universe" | tee /etc/apt/sources.list.d/jammy.list
|
|
109
|
-
apt-get update
|
|
110
|
-
`;
|
|
111
|
-
/**
|
|
112
|
-
* Post-install cleanup and xvfb / unity-hub wrapper setup. Runs as root; no user interpolation.
|
|
113
|
-
* Wrapper resolves Hub 3.20+ (/usr/lib/unityhub) vs legacy (/opt/unityhub) at runtime so apt
|
|
114
|
-
* upgrades that move the binary do not leave a stale path (exit 127).
|
|
115
|
-
*/
|
|
116
|
-
const LINUX_HUB_LINUX_INSTALL_POST = `#!/bin/sh
|
|
117
|
-
set -e
|
|
118
|
-
apt-get clean
|
|
119
|
-
sed -i 's/^\\(.*DISPLAY=:.*XAUTHORITY=.*\\)\\( "\\$@" \\)2>&1$/\\1\\2/' /usr/bin/xvfb-run
|
|
120
|
-
command -v unityhub >/dev/null || { echo "Unity Hub installation failed"; exit 1; }
|
|
121
|
-
hubPath=$(readlink -f "$(command -v unityhub)" 2>/dev/null || true)
|
|
122
|
-
if [ -z "$hubPath" ] || [ ! -x "$hubPath" ]; then
|
|
123
|
-
if [ -x /usr/lib/unityhub/unityhub ]; then
|
|
124
|
-
hubPath=/usr/lib/unityhub/unityhub
|
|
125
|
-
elif [ -x /opt/unityhub/unityhub ]; then
|
|
126
|
-
hubPath=/opt/unityhub/unityhub
|
|
127
|
-
else
|
|
128
|
-
echo "Failed to install Unity Hub"
|
|
129
|
-
exit 1
|
|
130
|
-
fi
|
|
131
|
-
fi
|
|
132
|
-
tee /usr/bin/unity-hub >/dev/null <<'WRAPPER'
|
|
133
|
-
#!/bin/bash
|
|
134
|
-
if [ -x /usr/lib/unityhub/unityhub ]; then
|
|
135
|
-
hubBin=/usr/lib/unityhub/unityhub
|
|
136
|
-
elif [ -x /opt/unityhub/unityhub ]; then
|
|
137
|
-
hubBin=/opt/unityhub/unityhub
|
|
138
|
-
else
|
|
139
|
-
hubBin=$(readlink -f "$(command -v unityhub)" 2>/dev/null || true)
|
|
140
|
-
fi
|
|
141
|
-
if [ -z "$hubBin" ] || [ ! -x "$hubBin" ]; then
|
|
142
|
-
echo "Unity Hub binary not found" >&2
|
|
143
|
-
exit 127
|
|
144
|
-
fi
|
|
145
|
-
exec xvfb-run --auto-servernum "$hubBin" "$@" 2>/dev/null
|
|
146
|
-
WRAPPER
|
|
147
|
-
chmod 777 /usr/bin/unity-hub
|
|
148
|
-
chmod -R 777 "$(dirname "$hubPath")"
|
|
149
|
-
`;
|
|
150
|
-
const LINUX_HUB_LINUX_APT_EXTRAS = [
|
|
151
|
-
'xvfb',
|
|
152
|
-
'ffmpeg',
|
|
153
|
-
'libgtk2.0-0',
|
|
154
|
-
'libglu1-mesa',
|
|
155
|
-
'libgconf-2-4',
|
|
156
|
-
'libncurses5',
|
|
157
|
-
'pulseaudio',
|
|
158
|
-
];
|
|
159
|
-
class UnityHub {
|
|
160
|
-
/** The path to the Unity Hub executable. */
|
|
161
|
-
executable;
|
|
162
|
-
/** The root directory of the Unity Hub installation. */
|
|
163
|
-
rootDirectory;
|
|
164
|
-
/** The file extension for the Unity editor executable. */
|
|
165
|
-
editorFileExtension;
|
|
166
|
-
logger = logging_1.Logger.instance;
|
|
167
|
-
constructor() {
|
|
168
|
-
switch (process.platform) {
|
|
169
|
-
case 'win32': {
|
|
170
|
-
const hubPath = process.env.UNITY_HUB_PATH ?? undefined;
|
|
171
|
-
if (hubPath !== undefined && hubPath.length > 0) {
|
|
172
|
-
this.executable = hubPath;
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
const pf64 = path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Unity Hub', 'Unity Hub.exe');
|
|
176
|
-
const pfx86 = path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Unity Hub', 'Unity Hub.exe');
|
|
177
|
-
// Windows ARM64 images install Hub under WOW64; x64 prefers 64-bit path when present.
|
|
178
|
-
if (process.arch === 'arm64') {
|
|
179
|
-
this.executable = pfx86;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
this.executable = fs.existsSync(pf64) ? pf64 : fs.existsSync(pfx86) ? pfx86 : pf64;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
this.rootDirectory = path.join(this.executable, '../');
|
|
186
|
-
this.editorFileExtension = '\\Editor\\Unity.exe';
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
case 'darwin':
|
|
190
|
-
this.executable = process.env.UNITY_HUB_PATH || '/Applications/Unity Hub.app/Contents/MacOS/Unity Hub';
|
|
191
|
-
this.rootDirectory = path.join(this.executable, '../../../');
|
|
192
|
-
this.editorFileExtension = '/Unity.app/Contents/MacOS/Unity';
|
|
193
|
-
break;
|
|
194
|
-
case 'linux':
|
|
195
|
-
this.refreshLinuxHubPaths();
|
|
196
|
-
this.editorFileExtension = '/Editor/Unity';
|
|
197
|
-
break;
|
|
198
|
-
default:
|
|
199
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
/** Re-resolve Linux Hub executable + root after install/upgrade (Hub 3.20 moved under /usr/lib). */
|
|
203
|
-
refreshLinuxHubPaths() {
|
|
204
|
-
this.executable = resolveLinuxHubExecutable();
|
|
205
|
-
this.rootDirectory = path.join(this.executable, '../');
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Some Hub builds (notably Windows headless) occasionally exit non-zero after streaming usable
|
|
209
|
-
* `editors --releases` / `editors -i` data. Tolerate only when the captured output parses the same
|
|
210
|
-
* way {@link ListAvailableReleases} / {@link ListInstalledEditors} would (avoids regex false positives).
|
|
211
|
-
*/
|
|
212
|
-
hubListingExitTolerable(args, hubOutput) {
|
|
213
|
-
if (!this.isHubEditorListingArgs(args)) {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
if (args.includes('--releases')) {
|
|
217
|
-
return this.parseAvailableReleasesFromHubText(hubOutput).length > 0;
|
|
218
|
-
}
|
|
219
|
-
if (args.includes('-i') || args.includes('--installed')) {
|
|
220
|
-
return hubOutput.includes('installed at');
|
|
221
|
-
}
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
isHubEditorListingArgs(args) {
|
|
225
|
-
return args.length > 0 && args[0] === 'editors' &&
|
|
226
|
-
(args.includes('--releases') || args.includes('-i') || args.includes('--installed'));
|
|
227
|
-
}
|
|
228
|
-
async delayMs(ms) {
|
|
229
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
230
|
-
}
|
|
231
|
-
/** Same parsing rules as {@link ListAvailableReleases}; must stay in sync. */
|
|
232
|
-
parseAvailableReleasesFromHubText(output) {
|
|
233
|
-
return output.split('\n')
|
|
234
|
-
.map(line => line.trim())
|
|
235
|
-
.map(line => {
|
|
236
|
-
const match = line.match(/^(\d{1,4}\.\d+\.\d+[abcfpx]?\d*)/);
|
|
237
|
-
return match ? match[1] : undefined;
|
|
238
|
-
})
|
|
239
|
-
.filter((line) => !!line && /^\d{1,4}\.\d+\.\d+[abcfpx]?\d*/.test(line))
|
|
240
|
-
.map(line => new unity_version_1.UnityVersion(line))
|
|
241
|
-
.sort((a, b) => unity_version_1.UnityVersion.compare(b, a));
|
|
242
|
-
}
|
|
243
|
-
/** Same parsing rules as {@link ListInstalledEditors}; must stay in sync. */
|
|
244
|
-
parseInstalledEditorsFromHubText(output) {
|
|
245
|
-
const paths = output.split('\n')
|
|
246
|
-
.filter(line => /installed at/.test(line))
|
|
247
|
-
.map(line => line.trim());
|
|
248
|
-
const editors = [];
|
|
249
|
-
const pattern = /(?<version>\d+\.\d+\.\d+[abcfpx]?\d*)\s*(?:\((?<arch>Apple silicon|Intel)\))?\s*,? installed at (?<editorPath>.*)/;
|
|
250
|
-
const matches = paths.map((line) => line.match(pattern)).filter(match => match && match.groups);
|
|
251
|
-
if (paths.length !== matches.length) {
|
|
252
|
-
throw new Error(`Failed to parse all installed Unity Editors!\n > paths: ${JSON.stringify(paths)}\n > matches: ${JSON.stringify(matches)}`);
|
|
253
|
-
}
|
|
254
|
-
for (const match of matches) {
|
|
255
|
-
if (match && match.groups && match.groups.version && match.groups.editorPath) {
|
|
256
|
-
const version = new unity_version_1.UnityVersion(match.groups.version, null, match.groups.arch === 'Apple silicon' ? 'ARM64' : match.groups.arch === 'Intel' ? 'X86_64' : undefined);
|
|
257
|
-
editors.push(new unity_editor_1.UnityEditor(path.normalize(match.groups.editorPath), version));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
editors.sort((a, b) => {
|
|
261
|
-
if (!a.version && !b.version) {
|
|
262
|
-
return 0;
|
|
263
|
-
}
|
|
264
|
-
if (!a.version) {
|
|
265
|
-
return 1;
|
|
266
|
-
}
|
|
267
|
-
if (!b.version) {
|
|
268
|
-
return -1;
|
|
269
|
-
}
|
|
270
|
-
return unity_version_1.UnityVersion.compare(b.version, a.version);
|
|
271
|
-
});
|
|
272
|
-
return editors;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Executes the Unity Hub command with the specified arguments.
|
|
276
|
-
* @param args Arguments to pass to the Unity Hub executable.
|
|
277
|
-
* @param options Logging and spawn options for this invocation.
|
|
278
|
-
* @returns The output from the command.
|
|
279
|
-
*/
|
|
280
|
-
async Exec(args, options = { silent: this.logger.logLevel > logging_1.LogLevel.CI, showCommand: this.logger.logLevel <= logging_1.LogLevel.CI }) {
|
|
281
|
-
return this.execImpl(args, options, 0);
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* @param listingRetryDepth 0 on first attempt; 1 after one listing-only retry (flaky Hub exits on Windows CI).
|
|
285
|
-
*/
|
|
286
|
-
async execImpl(args, options, listingRetryDepth) {
|
|
287
|
-
let output = '';
|
|
288
|
-
let exitCode = 0;
|
|
289
|
-
const filteredArgs = args.filter(arg => arg !== '--headless' && arg !== '--');
|
|
290
|
-
const executable = process.platform === 'linux' ? 'unity-hub' : this.executable;
|
|
291
|
-
const execArgs = process.platform === 'linux' ? ['--headless', ...filteredArgs] : ['--', '--headless', ...filteredArgs];
|
|
292
|
-
if (options.showCommand) {
|
|
293
|
-
this.logger.startGroup(`\x1b[34m${executable} ${execArgs.join(' ')}\x1b[0m`);
|
|
294
|
-
}
|
|
295
|
-
if (this.executable.includes(path.sep)) {
|
|
296
|
-
fs.accessSync(this.executable, fs.constants.R_OK | fs.constants.X_OK);
|
|
297
|
-
}
|
|
298
|
-
const ignoredLines = [
|
|
299
|
-
'This error originated either by throwing inside of an async function without a catch block',
|
|
300
|
-
'Unexpected error attempting to determine if executable file exists',
|
|
301
|
-
'dri3 extension not supported',
|
|
302
|
-
'Failed to connect to the bus:',
|
|
303
|
-
'Error: No modules found to install.',
|
|
304
|
-
'Checking for beta autoupdate feature for deb/rpm distributions',
|
|
305
|
-
'Found package-type: deb',
|
|
306
|
-
'XPC error for connection com.apple.backupd.sandbox.xpc: Connection invalid',
|
|
307
|
-
'Failed to execute the command due the following, please see \'-- --headless help\' for assistance.',
|
|
308
|
-
'Unable to move the cache: Access is denied.',
|
|
309
|
-
'Entities without keys will be embedded directly on the parent entity. If this is intentional, create a `keys` config for `UnityReleaseLabel` that always returns null.',
|
|
310
|
-
'https://bit.ly/2XbVrpR#15',
|
|
311
|
-
'Interaction is not allowed with the Security Server." (-25308)',
|
|
312
|
-
'Network service crashed, restarting service.',
|
|
313
|
-
'Invalid key: The GraphQL query at the field at',
|
|
314
|
-
'You have to request `id` or `_id` fields for all selection sets or create a custom `keys` config for `UnityReleaseLabel`.',
|
|
315
|
-
];
|
|
316
|
-
try {
|
|
317
|
-
exitCode = await new Promise((resolve, reject) => {
|
|
318
|
-
let isSettled = false; // Has the promise been settled (resolved or rejected)?
|
|
319
|
-
let isHubTaskCompleteSuccess = false; // Has the Unity Hub tasks completed successfully?
|
|
320
|
-
let isHubTaskCompleteFailed = false; // Has the Unity Hub tasks completed with failure?
|
|
321
|
-
let lineBuffer = ''; // Buffer for incomplete lines
|
|
322
|
-
const tasksCompleteMessages = [
|
|
323
|
-
'All Tasks Completed Successfully.',
|
|
324
|
-
'Completed with errors.'
|
|
325
|
-
];
|
|
326
|
-
const child = (0, child_process_1.spawn)(executable, execArgs, {
|
|
327
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
328
|
-
...(process.platform === 'win32' ? { windowsHide: true } : {}),
|
|
329
|
-
});
|
|
330
|
-
const sigintHandler = () => child.kill('SIGINT');
|
|
331
|
-
const sigtermHandler = () => child.kill('SIGTERM');
|
|
332
|
-
process.once('SIGINT', sigintHandler);
|
|
333
|
-
process.once('SIGTERM', sigtermHandler);
|
|
334
|
-
let hasCleanedUpListeners = false;
|
|
335
|
-
function removeListeners() {
|
|
336
|
-
if (hasCleanedUpListeners) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
hasCleanedUpListeners = true;
|
|
340
|
-
process.removeListener('SIGINT', sigintHandler);
|
|
341
|
-
process.removeListener('SIGTERM', sigtermHandler);
|
|
342
|
-
}
|
|
343
|
-
function processOutput(data) {
|
|
344
|
-
try {
|
|
345
|
-
const chunk = data.toString();
|
|
346
|
-
const fullChunk = lineBuffer + chunk;
|
|
347
|
-
const lines = fullChunk.split(/\r?\n/) // split by newline
|
|
348
|
-
.filter(line => line.length > 0); // filter out empty lines
|
|
349
|
-
if (!chunk.endsWith('\n')) {
|
|
350
|
-
lineBuffer = lines.pop() || '';
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
lineBuffer = '';
|
|
354
|
-
}
|
|
355
|
-
if (lines.some(line => tasksCompleteMessages.includes(line))) {
|
|
356
|
-
isHubTaskCompleteSuccess = lines.includes('All Tasks Completed Successfully.');
|
|
357
|
-
isHubTaskCompleteFailed = lines.includes('Completed with errors.');
|
|
358
|
-
if (child?.pid) {
|
|
359
|
-
try {
|
|
360
|
-
child.kill('SIGTERM');
|
|
361
|
-
setTimeout(() => {
|
|
362
|
-
try {
|
|
363
|
-
if (child?.pid && !child.killed) {
|
|
364
|
-
child.kill('SIGKILL');
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
catch {
|
|
368
|
-
// Ignore, process may have already exited
|
|
369
|
-
}
|
|
370
|
-
}, 1000);
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
// Ignore, process may have already exited
|
|
374
|
-
}
|
|
375
|
-
finally {
|
|
376
|
-
if (isHubTaskCompleteSuccess) {
|
|
377
|
-
settle(0);
|
|
378
|
-
}
|
|
379
|
-
else if (isHubTaskCompleteFailed) {
|
|
380
|
-
settle(1);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
settle(null);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
for (const line of lines) {
|
|
389
|
-
output += `${line}\n`;
|
|
390
|
-
if (!options.silent && !ignoredLines.some(ignored => line.includes(ignored))) {
|
|
391
|
-
process.stdout.write(`${line}\n`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
catch (error) {
|
|
396
|
-
if (error.code !== 'EPIPE') {
|
|
397
|
-
throw error;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
function flushOutput() {
|
|
402
|
-
try {
|
|
403
|
-
if (lineBuffer.length > 0) {
|
|
404
|
-
const lines = lineBuffer.split(/\r?\n/) // split by newline
|
|
405
|
-
.filter(line => line.length > 0); // filter out empty lines
|
|
406
|
-
lineBuffer = '';
|
|
407
|
-
const outputLines = lines.filter(line => !ignoredLines.some(ignored => line.includes(ignored)));
|
|
408
|
-
if (outputLines.some(line => tasksCompleteMessages.includes(line))) {
|
|
409
|
-
isHubTaskCompleteSuccess = outputLines.includes('All Tasks Completed Successfully.');
|
|
410
|
-
isHubTaskCompleteFailed = outputLines.includes('Completed with errors.');
|
|
411
|
-
}
|
|
412
|
-
for (const line of outputLines) {
|
|
413
|
-
output += `${line}\n`;
|
|
414
|
-
if (!options.silent) {
|
|
415
|
-
process.stdout.write(`${line}\n`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
if (error.code !== 'EPIPE') {
|
|
422
|
-
logging_1.Logger.instance.error(`Failed to process buffered output: ${error}`);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
function settle(code) {
|
|
427
|
-
if (isSettled) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
isSettled = true;
|
|
431
|
-
removeListeners();
|
|
432
|
-
flushOutput();
|
|
433
|
-
resolve(code === null ? 0 : code);
|
|
434
|
-
}
|
|
435
|
-
child.stdout.on('data', processOutput);
|
|
436
|
-
child.stderr.on('data', processOutput);
|
|
437
|
-
child.on('error', (error) => {
|
|
438
|
-
isSettled = true;
|
|
439
|
-
removeListeners();
|
|
440
|
-
flushOutput();
|
|
441
|
-
reject(error);
|
|
442
|
-
});
|
|
443
|
-
child.on('close', settle);
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
finally {
|
|
447
|
-
this.logger.endGroup();
|
|
448
|
-
const match = output.match(/Assertion (?<assert>.+) failed/g);
|
|
449
|
-
const retryConditions = [
|
|
450
|
-
'async hook stack has become corrupted',
|
|
451
|
-
'failed to download'
|
|
452
|
-
];
|
|
453
|
-
if (match ||
|
|
454
|
-
retryConditions.some(s => output.includes(s))) {
|
|
455
|
-
this.logger.warn(`Install failed, retrying...`);
|
|
456
|
-
return await this.execImpl(args, options, 0);
|
|
457
|
-
}
|
|
458
|
-
if (exitCode > 0) {
|
|
459
|
-
if (this.hubListingExitTolerable(args, output)) {
|
|
460
|
-
this.logger.warn(`Unity Hub exited with code ${exitCode} but produced usable listing output; continuing.`);
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
const error = output.match(/Error(?: given)?:\s*(.+)/);
|
|
464
|
-
const errorMessage = error && error[1] ? error[1] : 'Unknown Error';
|
|
465
|
-
switch (errorMessage) {
|
|
466
|
-
case 'No modules found to install.':
|
|
467
|
-
break;
|
|
468
|
-
default:
|
|
469
|
-
if (this.isHubEditorListingArgs(args) && listingRetryDepth < 1) {
|
|
470
|
-
this.logger.warn(`Unity Hub listing command failed (exit code ${exitCode}); retrying once after 2s...`);
|
|
471
|
-
await this.delayMs(2000);
|
|
472
|
-
return await this.execImpl(args, options, listingRetryDepth + 1);
|
|
473
|
-
}
|
|
474
|
-
this.logger.debug(output);
|
|
475
|
-
throw new Error(`Failed to execute Unity Hub (exit code: ${exitCode}) ${errorMessage}`);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
output = output.split('\n')
|
|
480
|
-
.filter(line => line.trim().length > 0)
|
|
481
|
-
.filter(line => !ignoredLines.some(ignored => line.includes(ignored)))
|
|
482
|
-
.join('\n');
|
|
483
|
-
}
|
|
484
|
-
return output;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Prints the installed Unity Hub version.
|
|
488
|
-
* @returns The installed Unity Hub version.
|
|
489
|
-
*/
|
|
490
|
-
async Version() {
|
|
491
|
-
const version = await this.getInstalledHubVersion();
|
|
492
|
-
return version.version;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Installs or updates the Unity Hub.
|
|
496
|
-
* If the Unity Hub is already installed, it will be updated to the latest version.
|
|
497
|
-
* @param autoUpdate If true, automatically updates the Unity Hub if it is already installed. Default is true.
|
|
498
|
-
* @returns The path to the Unity Hub executable.
|
|
499
|
-
*/
|
|
500
|
-
async Install(autoUpdate = true, version) {
|
|
501
|
-
if (autoUpdate && version) {
|
|
502
|
-
throw new Error('Cannot use autoUpdate with version.');
|
|
503
|
-
}
|
|
504
|
-
let isInstalled = false;
|
|
505
|
-
let installedVersion = undefined;
|
|
506
|
-
try {
|
|
507
|
-
await fs.promises.access(this.executable, fs.constants.X_OK);
|
|
508
|
-
installedVersion = await this.getInstalledHubVersion();
|
|
509
|
-
isInstalled = true;
|
|
510
|
-
}
|
|
511
|
-
catch {
|
|
512
|
-
await this.installHub(version);
|
|
513
|
-
}
|
|
514
|
-
if (isInstalled && autoUpdate) {
|
|
515
|
-
if (!installedVersion) {
|
|
516
|
-
installedVersion = await this.getInstalledHubVersion();
|
|
517
|
-
}
|
|
518
|
-
this.logger.ci(`Installed Unity Hub version: ${installedVersion.version}`);
|
|
519
|
-
let versionToInstall = null;
|
|
520
|
-
if (!version) {
|
|
521
|
-
try {
|
|
522
|
-
versionToInstall = await this.getLatestHubVersion();
|
|
523
|
-
if (versionToInstall) {
|
|
524
|
-
this.logger.ci(`Latest Unity Hub version: ${versionToInstall.version}`);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
catch (error) {
|
|
528
|
-
this.logger.warn(`Failed to get latest Unity Hub version: ${error}`);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
versionToInstall = (0, semver_1.coerce)(version);
|
|
533
|
-
}
|
|
534
|
-
if (versionToInstall === null ||
|
|
535
|
-
!versionToInstall ||
|
|
536
|
-
!(0, semver_1.valid)(versionToInstall)) {
|
|
537
|
-
if (version || process.platform !== 'linux') {
|
|
538
|
-
throw new Error(`Invalid Unity Hub version to install: ${versionToInstall}`);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
const mustInstall = Boolean(version) ||
|
|
542
|
-
(process.platform === 'linux' && !version) ||
|
|
543
|
-
(versionToInstall !== null && (0, semver_1.compare)(installedVersion, versionToInstall) < 0);
|
|
544
|
-
if (mustInstall) {
|
|
545
|
-
this.logger.info(`Updating Unity Hub from ${installedVersion.version} to ${versionToInstall?.version ?? 'latest'}...`);
|
|
546
|
-
if (process.platform === 'darwin') {
|
|
547
|
-
await (0, utilities_1.Exec)('sudo', ['rm', '-rf', this.rootDirectory], { silent: true, showCommand: true });
|
|
548
|
-
await this.installHub(version);
|
|
549
|
-
}
|
|
550
|
-
else if (process.platform === 'win32') {
|
|
551
|
-
const uninstaller = path.join(this.rootDirectory, 'Uninstall Unity Hub.exe');
|
|
552
|
-
await (0, utilities_1.Exec)('powershell', [
|
|
553
|
-
'-NoProfile',
|
|
554
|
-
'-Command',
|
|
555
|
-
`Start-Process -FilePath '${uninstaller}' -ArgumentList '/S' -Verb RunAs -Wait`
|
|
556
|
-
], { silent: true, showCommand: true });
|
|
557
|
-
await (0, utilities_1.DeleteDirectory)(this.rootDirectory);
|
|
558
|
-
await this.installHub(version);
|
|
559
|
-
}
|
|
560
|
-
else if (process.platform === 'linux') {
|
|
561
|
-
const hubPkg = this.unityHubAptPackageSpec(version);
|
|
562
|
-
const linuxExecOpts = { silent: true, showCommand: true };
|
|
563
|
-
await (0, utilities_1.Exec)('sudo', ['sh', '-c', LINUX_HUB_LINUX_UPDATE_REPO_BOOTSTRAP], linuxExecOpts);
|
|
564
|
-
await (0, utilities_1.Exec)('sudo', ['apt-get', 'install', '-y', '--no-install-recommends', '--only-upgrade', hubPkg], linuxExecOpts);
|
|
565
|
-
// Refresh xvfb wrapper after upgrades that move /opt → /usr/lib (Hub 3.20+).
|
|
566
|
-
await (0, utilities_1.Exec)('sudo', ['sh', '-c', LINUX_HUB_LINUX_INSTALL_POST], linuxExecOpts);
|
|
567
|
-
this.refreshLinuxHubPaths();
|
|
568
|
-
this.logger.info(`Unity Hub updated successfully.`);
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
this.logger.info(`Unity Hub is already installed and up to date.`);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (process.platform === 'linux') {
|
|
579
|
-
this.refreshLinuxHubPaths();
|
|
580
|
-
}
|
|
581
|
-
await fs.promises.access(this.executable, fs.constants.X_OK);
|
|
582
|
-
return this.executable;
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* APT package spec for unityhub (e.g. `unityhub` or `unityhub=3.6.0`). Validated; passed as argv, not shell-embedded.
|
|
586
|
-
*/
|
|
587
|
-
unityHubAptPackageSpec(version) {
|
|
588
|
-
if (version === undefined || version === null) {
|
|
589
|
-
return 'unityhub';
|
|
590
|
-
}
|
|
591
|
-
if (typeof version === 'object' && 'version' in version) {
|
|
592
|
-
const deb = version.version;
|
|
593
|
-
if (!LINUX_HUB_DEB_VERSION_RE.test(deb)) {
|
|
594
|
-
throw new Error(`Invalid Unity Hub apt version: ${deb}`);
|
|
595
|
-
}
|
|
596
|
-
return `unityhub=${deb}`;
|
|
597
|
-
}
|
|
598
|
-
const raw = String(version).trim();
|
|
599
|
-
if (raw.length === 0) {
|
|
600
|
-
return 'unityhub';
|
|
601
|
-
}
|
|
602
|
-
const pinned = (0, semver_1.coerce)(raw);
|
|
603
|
-
if (!pinned || !(0, semver_1.valid)(pinned)) {
|
|
604
|
-
throw new Error(`Invalid Unity Hub version for apt: ${raw}`);
|
|
605
|
-
}
|
|
606
|
-
const deb = pinned.version;
|
|
607
|
-
if (!LINUX_HUB_DEB_VERSION_RE.test(deb)) {
|
|
608
|
-
throw new Error(`Invalid Unity Hub apt version: ${deb}`);
|
|
609
|
-
}
|
|
610
|
-
return `unityhub=${deb}`;
|
|
611
|
-
}
|
|
612
|
-
async installHub(version) {
|
|
613
|
-
this.logger.ci(`Installing Unity Hub${version ? ' ' + version : ''}...`);
|
|
614
|
-
if (!version) {
|
|
615
|
-
switch (process.platform) {
|
|
616
|
-
case 'win32':
|
|
617
|
-
case 'darwin':
|
|
618
|
-
version = 'prod';
|
|
619
|
-
break;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
switch (process.platform) {
|
|
623
|
-
case 'win32': {
|
|
624
|
-
if (process.arch === 'arm64' && version !== 'prod') {
|
|
625
|
-
const pinned = (0, semver_1.coerce)(version);
|
|
626
|
-
if (!pinned || !(0, semver_1.valid)(pinned)) {
|
|
627
|
-
throw new Error(`Invalid Unity Hub version: ${String(version)}`);
|
|
628
|
-
}
|
|
629
|
-
if ((0, semver_1.compare)(pinned, MIN_NATIVE_WINDOWS_ARM64_HUB_VERSION) < 0) {
|
|
630
|
-
throw new Error(`Unity Hub ${pinned.version} does not ship a native Windows ARM64 installer ` +
|
|
631
|
-
`(minimum ${MIN_NATIVE_WINDOWS_ARM64_HUB_VERSION.version}). ` +
|
|
632
|
-
'Install without --hub-version to use the latest Hub, or use Windows x64 for older Hub versions.');
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
const winArch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
636
|
-
const setupFileName = `UnityHubSetup-${winArch}.exe`;
|
|
637
|
-
const url = `https://public-cdn.cloud.unity3d.com/hub/${version}/${setupFileName}`;
|
|
638
|
-
const downloadPath = path.join((0, utilities_1.GetTempDir)(), setupFileName);
|
|
639
|
-
await (0, utilities_1.DownloadFile)(url, downloadPath);
|
|
640
|
-
this.logger.info(`Running Unity Hub installer...`);
|
|
641
|
-
try {
|
|
642
|
-
await (0, utilities_1.Exec)('powershell', [
|
|
643
|
-
'-NoProfile',
|
|
644
|
-
'-Command',
|
|
645
|
-
`Start-Process -FilePath '${downloadPath}' -ArgumentList '/S' -Verb RunAs -Wait`
|
|
646
|
-
], { silent: true, showCommand: true });
|
|
647
|
-
}
|
|
648
|
-
finally {
|
|
649
|
-
if (fs.statSync(downloadPath).isFile()) {
|
|
650
|
-
await fs.promises.unlink(downloadPath);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
break;
|
|
654
|
-
}
|
|
655
|
-
case 'darwin': {
|
|
656
|
-
const baseUrl = `https://public-cdn.cloud.unity3d.com/hub/${version}`;
|
|
657
|
-
const url = `${baseUrl}/UnityHubSetup-${process.arch}.dmg`;
|
|
658
|
-
const downloadPath = path.join((0, utilities_1.GetTempDir)(), `UnityHubSetup-${process.arch}.dmg`);
|
|
659
|
-
await (0, utilities_1.DownloadFile)(url, downloadPath);
|
|
660
|
-
await fs.promises.chmod(downloadPath, 0o777);
|
|
661
|
-
let mountPoint = '';
|
|
662
|
-
this.logger.debug(`Mounting DMG...`);
|
|
663
|
-
try {
|
|
664
|
-
const output = await (0, utilities_1.Exec)('hdiutil', ['attach', downloadPath, '-nobrowse'], { silent: true, showCommand: true });
|
|
665
|
-
// can be "/Volumes/Unity Hub 3.13.1-arm64" or "/Volumes/Unity Hub 3.13.1"
|
|
666
|
-
const mountPointMatch = output.match(/\/Volumes\/Unity Hub.*$/m);
|
|
667
|
-
if (!mountPointMatch || mountPointMatch.length === 0) {
|
|
668
|
-
throw new Error(`Failed to find mount point in hdiutil output: ${output}`);
|
|
669
|
-
}
|
|
670
|
-
mountPoint = mountPointMatch[0];
|
|
671
|
-
this.logger.debug(`Mounted Unity Hub at ${mountPoint}`);
|
|
672
|
-
const appPath = path.join(mountPoint, 'Unity Hub.app');
|
|
673
|
-
this.logger.debug(`Copying ${appPath} to /Applications...`);
|
|
674
|
-
await fs.promises.access(appPath, fs.constants.R_OK | fs.constants.X_OK);
|
|
675
|
-
if (fs.existsSync('/Applications/Unity Hub.app')) {
|
|
676
|
-
await (0, utilities_1.Exec)('sudo', ['rm', '-rf', '/Applications/Unity Hub.app'], { silent: true, showCommand: true });
|
|
677
|
-
}
|
|
678
|
-
await (0, utilities_1.Exec)('sudo', ['cp', '-R', appPath, '/Applications/Unity Hub.app'], { silent: true, showCommand: true });
|
|
679
|
-
await (0, utilities_1.Exec)('sudo', ['chmod', '777', '/Applications/Unity Hub.app/Contents/MacOS/Unity Hub'], { silent: true, showCommand: true });
|
|
680
|
-
await (0, utilities_1.Exec)('sudo', ['mkdir', '-p', '/Library/Application Support/Unity'], { silent: true, showCommand: true });
|
|
681
|
-
await (0, utilities_1.Exec)('sudo', ['chmod', '777', '/Library/Application Support/Unity'], { silent: true, showCommand: true });
|
|
682
|
-
}
|
|
683
|
-
finally {
|
|
684
|
-
try {
|
|
685
|
-
if (mountPoint && mountPoint.length > 0) {
|
|
686
|
-
await (0, utilities_1.Exec)('hdiutil', ['detach', mountPoint, '-quiet'], { silent: true, showCommand: true });
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
finally {
|
|
690
|
-
if (fs.statSync(downloadPath).isFile()) {
|
|
691
|
-
await fs.promises.unlink(downloadPath);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
break;
|
|
696
|
-
}
|
|
697
|
-
case 'linux': {
|
|
698
|
-
const hubPkg = this.unityHubAptPackageSpec(version);
|
|
699
|
-
const linuxExecOpts = { silent: true, showCommand: true };
|
|
700
|
-
await (0, utilities_1.Exec)('sudo', ['sh', '-c', LINUX_HUB_LINUX_INSTALL_BOOTSTRAP], linuxExecOpts);
|
|
701
|
-
await (0, utilities_1.Exec)('sudo', ['apt-get', 'install', '-y', '--no-install-recommends', hubPkg, ...LINUX_HUB_LINUX_APT_EXTRAS], linuxExecOpts);
|
|
702
|
-
await (0, utilities_1.Exec)('sudo', ['sh', '-c', LINUX_HUB_LINUX_INSTALL_POST], linuxExecOpts);
|
|
703
|
-
this.refreshLinuxHubPaths();
|
|
704
|
-
break;
|
|
705
|
-
}
|
|
706
|
-
default:
|
|
707
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
708
|
-
}
|
|
709
|
-
await fs.promises.access(this.executable, fs.constants.X_OK);
|
|
710
|
-
const installedVersion = await this.getInstalledHubVersion();
|
|
711
|
-
this.logger.info(`Unity Hub ${installedVersion} installed successfully.`);
|
|
712
|
-
}
|
|
713
|
-
async getInstalledHubVersion() {
|
|
714
|
-
let asarPath = undefined;
|
|
715
|
-
switch (process.platform) {
|
|
716
|
-
case 'darwin': {
|
|
717
|
-
asarPath = path.join(this.rootDirectory, 'Contents', 'Resources', 'app.asar');
|
|
718
|
-
break;
|
|
719
|
-
}
|
|
720
|
-
default: {
|
|
721
|
-
asarPath = path.join(this.rootDirectory, 'resources', 'app.asar');
|
|
722
|
-
break;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
try {
|
|
726
|
-
await fs.promises.access(asarPath, fs.constants.R_OK);
|
|
727
|
-
}
|
|
728
|
-
catch {
|
|
729
|
-
throw new Error('Unity Hub is not installed.');
|
|
730
|
-
}
|
|
731
|
-
asar.uncacheAll();
|
|
732
|
-
const fileBuffer = asar.extractFile(asarPath, 'package.json').toString('utf-8');
|
|
733
|
-
let packageJson;
|
|
734
|
-
try {
|
|
735
|
-
packageJson = JSON.parse(fileBuffer);
|
|
736
|
-
}
|
|
737
|
-
catch (error) {
|
|
738
|
-
throw new Error(`Failed to parse Unity Hub package.json: ${error}\n${fileBuffer}`);
|
|
739
|
-
}
|
|
740
|
-
const version = (0, semver_1.coerce)(packageJson.version);
|
|
741
|
-
if (!version || !(0, semver_1.valid)(version)) {
|
|
742
|
-
throw new Error(`Failed to parse Unity Hub version: ${packageJson.version}`);
|
|
743
|
-
}
|
|
744
|
-
return version;
|
|
745
|
-
}
|
|
746
|
-
async getLatestHubVersion() {
|
|
747
|
-
let url = undefined;
|
|
748
|
-
switch (process.platform) {
|
|
749
|
-
case 'win32':
|
|
750
|
-
url = 'https://public-cdn.cloud.unity3d.com/hub/prod/latest.yml';
|
|
751
|
-
break;
|
|
752
|
-
case 'darwin':
|
|
753
|
-
url = 'https://public-cdn.cloud.unity3d.com/hub/prod/latest-mac.yml';
|
|
754
|
-
break;
|
|
755
|
-
case 'linux':
|
|
756
|
-
return null;
|
|
757
|
-
default:
|
|
758
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
759
|
-
}
|
|
760
|
-
const data = await (0, utilities_1.HttpsGetText)(url);
|
|
761
|
-
const parsed = yaml.parse(data);
|
|
762
|
-
const version = (0, semver_1.coerce)(parsed.version);
|
|
763
|
-
if (!version || !(0, semver_1.valid)(version)) {
|
|
764
|
-
throw new Error(`Failed to parse latest Unity Hub version: ${parsed.version}`);
|
|
765
|
-
}
|
|
766
|
-
return version;
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Returns the path where the Unity editors will be installed.
|
|
770
|
-
* @returns The editor install path.
|
|
771
|
-
*/
|
|
772
|
-
async GetInstallPath() {
|
|
773
|
-
const result = (await this.Exec(['install-path', '--get'])).trim();
|
|
774
|
-
if (!result || result.length === 0) {
|
|
775
|
-
throw new Error('Failed to get Unity Hub install path.');
|
|
776
|
-
}
|
|
777
|
-
return result;
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Sets the path where Unity editors will be installed.
|
|
781
|
-
* @param installPath The install path to set when installing Unity editors.
|
|
782
|
-
*/
|
|
783
|
-
async SetInstallPath(installPath) {
|
|
784
|
-
await fs.promises.mkdir(installPath, { recursive: true });
|
|
785
|
-
await this.Exec(['install-path', '--set', installPath]);
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Locate and associate an installed editor from a stipulated path.
|
|
789
|
-
* @param editorPath The path to the Unity Editor installation.
|
|
790
|
-
*/
|
|
791
|
-
async AddEditor(editorPath) {
|
|
792
|
-
await fs.promises.access(editorPath, fs.constants.R_OK | fs.constants.X_OK);
|
|
793
|
-
await this.Exec(['editors', '--add', editorPath]);
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Attempts to find or install the specified Unity Editor version with the requested modules.
|
|
797
|
-
* @param unityVersion The Unity version to find or install.
|
|
798
|
-
* @param modules The modules to install alongside the editor.
|
|
799
|
-
* @param channels The channels to consider when searching for the editor. Can be 'f' (final), 'p' (patch), 'b' (beta), 'a' (alpha), or 'x' (experimental). Default is ['f'].
|
|
800
|
-
* @returns The path to the Unity Editor executable.
|
|
801
|
-
*/
|
|
802
|
-
async GetEditor(unityVersion, modules = [], channels = ['f']) {
|
|
803
|
-
const retryErrorMessages = [
|
|
804
|
-
'Editor already installed in this location',
|
|
805
|
-
'failed to download. Error given: Request timeout'
|
|
806
|
-
];
|
|
807
|
-
this.logger.ci(`Getting release info for Unity ${unityVersion.toString()}...`);
|
|
808
|
-
let resolvedVersion = unityVersion;
|
|
809
|
-
if (!resolvedVersion.isLegacy()) {
|
|
810
|
-
// Hub list is a fast path only. Misses must fall through to the Releases API —
|
|
811
|
-
// do not fail-closed until both Hub match and API resolution have failed.
|
|
812
|
-
if (!resolvedVersion.isFullyQualified()) {
|
|
813
|
-
try {
|
|
814
|
-
const releases = await this.ListAvailableReleases();
|
|
815
|
-
logging_1.Logger.instance.debug(`Found ${releases.length} available Unity releases, searching channels: ${channels.join(', ')}`);
|
|
816
|
-
resolvedVersion = resolvedVersion.findMatch(releases, channels);
|
|
817
|
-
}
|
|
818
|
-
catch (hubMatchError) {
|
|
819
|
-
this.logger.debug(`No Hub list match for ${resolvedVersion.toString()} (channels: ${channels.join(', ')}); trying Releases API...\n${hubMatchError}`);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
if (!resolvedVersion.changeset) {
|
|
823
|
-
try {
|
|
824
|
-
const unityReleaseInfo = await this.GetEditorReleaseInfo(resolvedVersion, channels);
|
|
825
|
-
resolvedVersion = new unity_version_1.UnityVersion(unityReleaseInfo.version, unityReleaseInfo.shortRevision, resolvedVersion.architecture);
|
|
826
|
-
}
|
|
827
|
-
catch (error) {
|
|
828
|
-
// Fail closed for partial versions: never Hub-install "6000.6" and hope it picks a beta.
|
|
829
|
-
if (!resolvedVersion.isFullyQualified()) {
|
|
830
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
831
|
-
throw new Error(`Failed to resolve Unity ${unityVersion.toString()} for channel(s) [${channels.join(', ')}]: ${msg}`);
|
|
832
|
-
}
|
|
833
|
-
this.logger.warn(`Failed to get Unity release info for ${resolvedVersion.toString()}! falling back to legacy search...\n${error}`);
|
|
834
|
-
try {
|
|
835
|
-
resolvedVersion = await this.fallbackVersionLookup(resolvedVersion);
|
|
836
|
-
}
|
|
837
|
-
catch (fallbackError) {
|
|
838
|
-
this.logger.warn(`Failed to lookup changeset for Unity ${resolvedVersion.toString()}!\n${fallbackError}`);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
if (!resolvedVersion.isLegacy() && !resolvedVersion.isFullyQualified()) {
|
|
844
|
-
throw new Error(`Refusing to install non-fully-qualified Unity version ${resolvedVersion.toString()} without a resolved release. ` +
|
|
845
|
-
`Use a fully-qualified version or --channel matching an available stream.`);
|
|
846
|
-
}
|
|
847
|
-
const allowPartialMatches = !resolvedVersion.isFullyQualified();
|
|
848
|
-
let editorPath = await this.checkInstalledEditors(resolvedVersion, false, undefined, allowPartialMatches);
|
|
849
|
-
unityVersion = resolvedVersion;
|
|
850
|
-
let installDir = undefined;
|
|
851
|
-
if (!editorPath) {
|
|
852
|
-
try {
|
|
853
|
-
installDir = await this.installUnity(unityVersion, modules);
|
|
854
|
-
}
|
|
855
|
-
catch (error) {
|
|
856
|
-
const errMessage = error instanceof Error ? error.message : String(error);
|
|
857
|
-
if (retryErrorMessages.some((msg) => errMessage.includes(msg))) {
|
|
858
|
-
if (editorPath) {
|
|
859
|
-
await (0, utilities_1.DeleteDirectory)(editorPath);
|
|
860
|
-
}
|
|
861
|
-
if (installDir) {
|
|
862
|
-
await (0, utilities_1.DeleteDirectory)(installDir);
|
|
863
|
-
}
|
|
864
|
-
installDir = await this.installUnity(unityVersion, modules);
|
|
865
|
-
}
|
|
866
|
-
else {
|
|
867
|
-
throw error;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
editorPath = await this.checkInstalledEditors(unityVersion, true, installDir);
|
|
871
|
-
}
|
|
872
|
-
if (!editorPath) {
|
|
873
|
-
throw new Error(`Failed to find or install Unity Editor: ${unityVersion.toString()}`);
|
|
874
|
-
}
|
|
875
|
-
await fs.promises.access(editorPath, fs.constants.X_OK);
|
|
876
|
-
await this.patchBeeBackend(editorPath);
|
|
877
|
-
if (unityVersion.isLegacy() || modules.length === 0) {
|
|
878
|
-
return new unity_editor_1.UnityEditor(path.normalize(editorPath), unityVersion);
|
|
879
|
-
}
|
|
880
|
-
try {
|
|
881
|
-
this.logger.info(`Validating installed modules for Unity ${unityVersion.toString()}...`);
|
|
882
|
-
const [installedModules, additionalModules] = await this.checkEditorModules(editorPath, unityVersion, modules);
|
|
883
|
-
if (installedModules && installedModules.length > 0) {
|
|
884
|
-
this.logger.info(`Installed Modules:`);
|
|
885
|
-
for (const module of installedModules) {
|
|
886
|
-
this.logger.info(` > ${module}`);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
if (additionalModules && additionalModules.length > 0) {
|
|
890
|
-
this.logger.info(`Additional Modules:`);
|
|
891
|
-
for (const module of additionalModules) {
|
|
892
|
-
this.logger.info(` > ${module}`);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
catch (error) {
|
|
897
|
-
const errMessage = error instanceof Error ? error.message : String(error);
|
|
898
|
-
if (errMessage.includes(`No modules found`)) {
|
|
899
|
-
await (0, utilities_1.DeleteDirectory)(editorPath);
|
|
900
|
-
await this.GetEditor(unityVersion, modules);
|
|
901
|
-
}
|
|
902
|
-
else {
|
|
903
|
-
throw error;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
return new unity_editor_1.UnityEditor(path.normalize(editorPath), unityVersion);
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* Lists the installed Unity Editors.
|
|
910
|
-
* @returns A list of installed Unity Editor versions and their paths.
|
|
911
|
-
*/
|
|
912
|
-
async ListInstalledEditors() {
|
|
913
|
-
const output = await this.Exec(['editors', '-i']);
|
|
914
|
-
return this.parseInstalledEditorsFromHubText(output);
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Lists the available Unity releases.
|
|
918
|
-
* @returns A list of available Unity release versions.
|
|
919
|
-
*/
|
|
920
|
-
async ListAvailableReleases() {
|
|
921
|
-
const output = await this.Exec(['editors', '--releases']);
|
|
922
|
-
return this.parseAvailableReleasesFromHubText(output);
|
|
923
|
-
}
|
|
924
|
-
async checkInstalledEditors(unityVersion, failOnEmpty, installDir = undefined, allowPartialMatches = true) {
|
|
925
|
-
let editorPath = undefined;
|
|
926
|
-
if (!installDir) {
|
|
927
|
-
const editors = await this.ListInstalledEditors();
|
|
928
|
-
if (editors && editors.length > 0) {
|
|
929
|
-
// Prefer exact version match first
|
|
930
|
-
const exactEditor = editors.find(e => e.version && e.version.version === unityVersion.version);
|
|
931
|
-
if (exactEditor) {
|
|
932
|
-
editorPath = exactEditor.editorPath;
|
|
933
|
-
}
|
|
934
|
-
else if (allowPartialMatches) {
|
|
935
|
-
// Fallback: semver satisfies
|
|
936
|
-
const versionEditors = editors.filter(e => e.version && unityVersion.satisfies(e.version));
|
|
937
|
-
if (versionEditors.length === 0) {
|
|
938
|
-
return undefined;
|
|
939
|
-
}
|
|
940
|
-
// ListInstalledEditors already returns editors sorted descending by version.
|
|
941
|
-
for (const editor of versionEditors) {
|
|
942
|
-
if (!editor.version || !editor.editorPath) {
|
|
943
|
-
continue;
|
|
944
|
-
}
|
|
945
|
-
// If no architecture requested or editor has no arch info, accept it
|
|
946
|
-
if (!unityVersion.architecture || !editor.version.architecture) {
|
|
947
|
-
editorPath = editor.editorPath;
|
|
948
|
-
break;
|
|
949
|
-
}
|
|
950
|
-
// Exact architecture match
|
|
951
|
-
if (unityVersion.architecture === editor.version.architecture) {
|
|
952
|
-
editorPath = editor.editorPath;
|
|
953
|
-
break;
|
|
954
|
-
}
|
|
955
|
-
// Fallback: check for architecture suffix in path (e.g., -arm64)
|
|
956
|
-
if (unityVersion.architecture && editor.editorPath.toLowerCase().includes(`-${unityVersion.architecture.toLowerCase()}`)) {
|
|
957
|
-
editorPath = editor.editorPath;
|
|
958
|
-
break;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
else {
|
|
965
|
-
if (process.platform == 'win32') {
|
|
966
|
-
editorPath = path.join(installDir, 'Unity.exe');
|
|
967
|
-
}
|
|
968
|
-
else {
|
|
969
|
-
editorPath = installDir;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
if (!editorPath) {
|
|
973
|
-
if (failOnEmpty) {
|
|
974
|
-
throw new Error(`Failed to find installed Unity Editor: ${unityVersion.toString()}`);
|
|
975
|
-
}
|
|
976
|
-
else {
|
|
977
|
-
return undefined;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
if (process.platform === 'darwin') {
|
|
981
|
-
editorPath = path.join(editorPath, '/Contents/MacOS/Unity');
|
|
982
|
-
}
|
|
983
|
-
try {
|
|
984
|
-
await fs.promises.access(editorPath, fs.constants.R_OK | fs.constants.X_OK);
|
|
985
|
-
}
|
|
986
|
-
catch (error) {
|
|
987
|
-
throw new Error(`Failed to find installed Unity Editor: ${unityVersion.toString()}\n > ${error}`);
|
|
988
|
-
}
|
|
989
|
-
this.logger.debug(`Found installed editor: "${editorPath}"`);
|
|
990
|
-
return editorPath;
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Patches the Bee Backend for Unity Linux Editor.
|
|
994
|
-
* https://discussions.unity.com/t/linux-editor-stuck-on-loading-because-of-bee-backend-w-workaround/854480
|
|
995
|
-
* @param editorPath The path to the Unity Editor executable.
|
|
996
|
-
*/
|
|
997
|
-
async patchBeeBackend(editorPath) {
|
|
998
|
-
if (process.platform === 'linux') {
|
|
999
|
-
const dataPath = path.join(path.dirname(editorPath), 'Data');
|
|
1000
|
-
const beeBackend = path.join(dataPath, 'bee_backend');
|
|
1001
|
-
const dotBeeBackend = path.join(dataPath, '.bee_backend');
|
|
1002
|
-
if (fs.existsSync(beeBackend) && !fs.existsSync(dotBeeBackend)) {
|
|
1003
|
-
this.logger.ci(`Patching Unity Linux Editor for Bee Backend...`);
|
|
1004
|
-
await fs.promises.rename(beeBackend, dotBeeBackend);
|
|
1005
|
-
const wrapperSource = `#!/bin/bash
|
|
1006
|
-
# https://discussions.unity.com/t/linux-editor-stuck-on-loading-because-of-bee-backend-w-workaround/854480
|
|
1007
|
-
set -e
|
|
1008
|
-
args=("$@")
|
|
1009
|
-
for ((i = 0; i < "\${#args[@]}"; ++i)); do
|
|
1010
|
-
case \${args[i]} in
|
|
1011
|
-
--stdin-canary)
|
|
1012
|
-
unset "args[i]"
|
|
1013
|
-
break
|
|
1014
|
-
;;
|
|
1015
|
-
esac
|
|
1016
|
-
done
|
|
1017
|
-
"$(dirname "$0")/.$(basename "$0")" "\${args[@]}"`;
|
|
1018
|
-
await fs.promises.writeFile(beeBackend, wrapperSource, { encoding: 'utf-8', mode: 0o755 });
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Gets the specified Unity release info from the Unity Releases API.
|
|
1024
|
-
* Supports querying by exact version or by prefix (e.g., "2020", "2020.1", "2021.x", "2021.3.x").
|
|
1025
|
-
* @param unityVersion The Unity version to get the release info for.
|
|
1026
|
-
* @param channels Letter channels to accept (`f`, `p`, `b`, `a`, `x`). Default stable-only.
|
|
1027
|
-
* @returns The Unity release info.
|
|
1028
|
-
*/
|
|
1029
|
-
async GetEditorReleaseInfo(unityVersion, channels = ['f']) {
|
|
1030
|
-
// Prefer querying the releases API with the exact fully-qualified Unity version (e.g., 2022.3.10f1).
|
|
1031
|
-
// If we don't have a fully-qualified version, use the most specific prefix available:
|
|
1032
|
-
// - "YYYY.M" when provided (e.g., 6000.1)
|
|
1033
|
-
// - otherwise "YYYY"
|
|
1034
|
-
const fullUnityVersionPattern = /^\d{1,4}\.\d+\.\d+[abcfpx]\d+$/;
|
|
1035
|
-
let version;
|
|
1036
|
-
if (fullUnityVersionPattern.test(unityVersion.version)) {
|
|
1037
|
-
version = unityVersion.version;
|
|
1038
|
-
}
|
|
1039
|
-
else {
|
|
1040
|
-
const match = unityVersion.version.match(/^(\d{1,4})(?:\.(\d+))?/);
|
|
1041
|
-
if (match) {
|
|
1042
|
-
version = match[2] ? `${match[1]}.${match[2]}` : match[1];
|
|
1043
|
-
}
|
|
1044
|
-
else {
|
|
1045
|
-
version = unityVersion.version.split('.')[0];
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
const releasesClient = new unity_releases_api_1.UnityReleasesClient();
|
|
1049
|
-
const channelSet = new Set(channels.map(c => c.toLowerCase()));
|
|
1050
|
-
function getPlatform() {
|
|
1051
|
-
switch (process.platform) {
|
|
1052
|
-
case 'darwin':
|
|
1053
|
-
return ['MAC_OS'];
|
|
1054
|
-
case 'linux':
|
|
1055
|
-
return ['LINUX'];
|
|
1056
|
-
case 'win32':
|
|
1057
|
-
return ['WINDOWS'];
|
|
1058
|
-
default:
|
|
1059
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
function releaseChannelLetter(releaseVersion) {
|
|
1063
|
-
const m = /^(\d{1,4})\.(\d+)\.(\d+)([abcfpx])(\d+)$/.exec(releaseVersion);
|
|
1064
|
-
return m?.[4];
|
|
1065
|
-
}
|
|
1066
|
-
const request = {
|
|
1067
|
-
url: '/unity/editor/release/v1/releases',
|
|
1068
|
-
query: {
|
|
1069
|
-
version: version,
|
|
1070
|
-
architecture: [unityVersion.architecture],
|
|
1071
|
-
platform: getPlatform(),
|
|
1072
|
-
limit: 10,
|
|
1073
|
-
order: 'RELEASE_DATE_DESC',
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
this.logger.debug(`Get Unity Release: ${JSON.stringify(request, null, 2)}`);
|
|
1077
|
-
async function getRelease() {
|
|
1078
|
-
const { data, error } = await releasesClient.api.Release.getUnityReleases(request);
|
|
1079
|
-
if (error) {
|
|
1080
|
-
throw new Error(`Failed to get Unity releases: ${JSON.stringify(error, null, 2)}`);
|
|
1081
|
-
}
|
|
1082
|
-
if (!data || !data.results || data.results.length === 0) {
|
|
1083
|
-
throw new Error(`No Unity releases found for version: ${version}`);
|
|
1084
|
-
}
|
|
1085
|
-
const releases = (data.results || [])
|
|
1086
|
-
.filter((release) => {
|
|
1087
|
-
const v = release.version;
|
|
1088
|
-
if (v == null || v === '') {
|
|
1089
|
-
return false;
|
|
1090
|
-
}
|
|
1091
|
-
// Exact FQ request: accept that row regardless of channel filter.
|
|
1092
|
-
if (fullUnityVersionPattern.test(unityVersion.version) && v === unityVersion.version) {
|
|
1093
|
-
return true;
|
|
1094
|
-
}
|
|
1095
|
-
const letter = releaseChannelLetter(v);
|
|
1096
|
-
return letter != null && channelSet.has(letter);
|
|
1097
|
-
})
|
|
1098
|
-
.map(release => ({
|
|
1099
|
-
unityRelease: release,
|
|
1100
|
-
unityVersion: new unity_version_1.UnityVersion(release.version, release.shortRevision, unityVersion.architecture)
|
|
1101
|
-
}));
|
|
1102
|
-
if (releases.length === 0) {
|
|
1103
|
-
const channelList = [...channelSet].join(',');
|
|
1104
|
-
throw new Error(`No suitable Unity releases (channels: ${channelList}) found for version: ${version}` +
|
|
1105
|
-
(channelSet.has('f') && channelSet.size === 1
|
|
1106
|
-
? `. No stable (f) release for ${version}; use --channel b/a or a fully-qualified version.`
|
|
1107
|
-
: ''));
|
|
1108
|
-
}
|
|
1109
|
-
releases.sort((a, b) => unity_version_1.UnityVersion.compare(b.unityVersion, a.unityVersion));
|
|
1110
|
-
logging_1.Logger.instance.debug(`Found ${releases.length} matching Unity releases for version: ${version}`);
|
|
1111
|
-
releases.forEach(release => {
|
|
1112
|
-
logging_1.Logger.instance.debug(` - ${release.unityRelease.version} (${release.unityRelease.shortRevision}) - ${release.unityRelease.recommended}`);
|
|
1113
|
-
});
|
|
1114
|
-
const latest = releases[0].unityRelease;
|
|
1115
|
-
return latest;
|
|
1116
|
-
}
|
|
1117
|
-
try {
|
|
1118
|
-
return await getRelease();
|
|
1119
|
-
}
|
|
1120
|
-
catch (error) {
|
|
1121
|
-
if (error instanceof Error && error.message.includes('fetch failed')) {
|
|
1122
|
-
// Transient network error, retry once
|
|
1123
|
-
return await getRelease();
|
|
1124
|
-
}
|
|
1125
|
-
throw new Error(`Failed to get Unity releases: ${error}`);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
async fallbackVersionLookup(unityVersion) {
|
|
1129
|
-
if (!unityVersion.isFullyQualified()) {
|
|
1130
|
-
throw new Error(`Cannot lookup changeset for non-fully-qualified Unity version: ${unityVersion.toString()}`);
|
|
1131
|
-
}
|
|
1132
|
-
const url = `https://unity.com/releases/editor/whats-new/${unityVersion.version}`;
|
|
1133
|
-
this.logger.debug(`Fetching release page: "${url}"`);
|
|
1134
|
-
let response;
|
|
1135
|
-
try {
|
|
1136
|
-
response = await fetch(url);
|
|
1137
|
-
}
|
|
1138
|
-
catch (error) {
|
|
1139
|
-
this.logger.warn(`Failed to fetch changeset for Unity ${unityVersion.toString()}: ${error}`);
|
|
1140
|
-
return unityVersion;
|
|
1141
|
-
}
|
|
1142
|
-
const responseText = await response.text();
|
|
1143
|
-
if (!response.ok) {
|
|
1144
|
-
this.logger.info(responseText);
|
|
1145
|
-
}
|
|
1146
|
-
if (!response.ok) {
|
|
1147
|
-
throw new Error(`Failed to fetch changeset for Unity ${unityVersion.toString()} [${response.status}] "${url}"`);
|
|
1148
|
-
}
|
|
1149
|
-
this.logger.debug(`Release page content: \n${responseText}`);
|
|
1150
|
-
const match = responseText.match(/unityhub:\/\/(?<version>\d+\.\d+\.\d+[abcfpx]?\d*)\/(?<changeset>[a-zA-Z0-9]+)/);
|
|
1151
|
-
if (match && match.groups && match.groups.version && match.groups.changeset) {
|
|
1152
|
-
return new unity_version_1.UnityVersion(match.groups.version, match.groups.changeset, unityVersion.architecture);
|
|
1153
|
-
}
|
|
1154
|
-
this.logger.error(`Failed to find changeset for Unity ${unityVersion.toString()}`);
|
|
1155
|
-
return unityVersion;
|
|
1156
|
-
}
|
|
1157
|
-
async checkEditorModules(editorPath, unityVersion, modules) {
|
|
1158
|
-
let args = ['install-modules', '--version', unityVersion.version];
|
|
1159
|
-
if (unityVersion.architecture) {
|
|
1160
|
-
args.push('-a', unityVersion.architecture.toLowerCase());
|
|
1161
|
-
}
|
|
1162
|
-
for (const module of modules) {
|
|
1163
|
-
args.push('-m', module);
|
|
1164
|
-
}
|
|
1165
|
-
const editorRootPath = unity_editor_1.UnityEditor.GetEditorRootPath(editorPath);
|
|
1166
|
-
const modulesPath = path.join(editorRootPath, 'modules.json');
|
|
1167
|
-
this.logger.debug(`Editor Modules Manifest:\n > "${modulesPath}"`);
|
|
1168
|
-
const output = await this.Exec([...args, '--cm'], { showCommand: true, silent: false });
|
|
1169
|
-
const moduleMatches = output.matchAll(/Omitting module (?<module>.+) because it's already installed/g);
|
|
1170
|
-
if (moduleMatches) {
|
|
1171
|
-
const omittedModules = [...moduleMatches].map(match => match.groups?.module);
|
|
1172
|
-
for (const module of omittedModules) {
|
|
1173
|
-
if (module && !modules.includes(module)) {
|
|
1174
|
-
modules.push(module);
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
const installedModules = [...modules];
|
|
1179
|
-
const additionalModules = [];
|
|
1180
|
-
const additionalModulesJson = await this.getModulesContent(modulesPath);
|
|
1181
|
-
if (additionalModulesJson.length > 0) {
|
|
1182
|
-
for (const module of additionalModulesJson) {
|
|
1183
|
-
if (module.category === "Platforms" && module.visible === true) {
|
|
1184
|
-
if (!installedModules.includes(module.id)) {
|
|
1185
|
-
additionalModules.push(module.id);
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
return [installedModules, additionalModules];
|
|
1191
|
-
}
|
|
1192
|
-
async getModulesContent(modulesPath) {
|
|
1193
|
-
const modulesContent = await (0, utilities_1.ReadFileContents)(modulesPath);
|
|
1194
|
-
return JSON.parse(modulesContent);
|
|
1195
|
-
}
|
|
1196
|
-
async installUnity(unityVersion, modules) {
|
|
1197
|
-
if (unityVersion.isLegacy()) {
|
|
1198
|
-
return await this.installUnity4x(unityVersion);
|
|
1199
|
-
}
|
|
1200
|
-
if (process.platform === 'linux') {
|
|
1201
|
-
const arch = process.arch === 'x64' ? 'amd64' : process.arch === 'arm64' ? 'arm64' : process.arch;
|
|
1202
|
-
// install older versions of libssl for older Unity versions
|
|
1203
|
-
if (['2019.1', '2019.2'].some(v => unityVersion.version.startsWith(v))) {
|
|
1204
|
-
const url = `https://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.0.0_1.0.2g-1ubuntu4.20_${arch}.deb`;
|
|
1205
|
-
const downloadPath = path.join((0, utilities_1.GetTempDir)(), `libssl1.0.0_1.0.2g-1ubuntu4.20_${arch}.deb`);
|
|
1206
|
-
await (0, utilities_1.DownloadFile)(url, downloadPath);
|
|
1207
|
-
try {
|
|
1208
|
-
await (0, utilities_1.Exec)('sudo', ['dpkg', '-i', downloadPath]);
|
|
1209
|
-
}
|
|
1210
|
-
finally {
|
|
1211
|
-
fs.promises.unlink(downloadPath);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
else if (['2019.3', '2019.4'].some(v => unityVersion.version.startsWith(v)) ||
|
|
1215
|
-
unityVersion.version.startsWith('2020.') ||
|
|
1216
|
-
unityVersion.version.startsWith('2021.')) {
|
|
1217
|
-
const url = `https://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.0g-2ubuntu4_${arch}.deb`;
|
|
1218
|
-
const downloadPath = path.join((0, utilities_1.GetTempDir)(), `libssl1.1_1.1.0g-2ubuntu4_${arch}.deb`);
|
|
1219
|
-
await (0, utilities_1.DownloadFile)(url, downloadPath);
|
|
1220
|
-
try {
|
|
1221
|
-
await (0, utilities_1.Exec)('sudo', ['dpkg', '-i', downloadPath]);
|
|
1222
|
-
}
|
|
1223
|
-
finally {
|
|
1224
|
-
fs.promises.unlink(downloadPath);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
this.logger.ci(`Installing Unity ${unityVersion.toString()}...`);
|
|
1229
|
-
const args = ['install', '--version', unityVersion.version];
|
|
1230
|
-
if (unityVersion.changeset) {
|
|
1231
|
-
args.push('--changeset', unityVersion.changeset);
|
|
1232
|
-
}
|
|
1233
|
-
if (unityVersion.architecture) {
|
|
1234
|
-
args.push('-a', unityVersion.architecture.toLowerCase());
|
|
1235
|
-
}
|
|
1236
|
-
if (modules.length > 0) {
|
|
1237
|
-
for (const module of modules) {
|
|
1238
|
-
this.logger.ci(` > with module: ${module}`);
|
|
1239
|
-
args.push('-m', module);
|
|
1240
|
-
}
|
|
1241
|
-
args.push('--cm');
|
|
1242
|
-
}
|
|
1243
|
-
const output = await this.Exec(args, { showCommand: true, silent: false });
|
|
1244
|
-
if (output.includes(`Error while installing an editor or a module from changeset`) ||
|
|
1245
|
-
output.includes(`failed to download.`)) {
|
|
1246
|
-
throw new Error(`Failed to install Unity ${unityVersion.toString()}`);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
async installUnity4x(unityVersion) {
|
|
1250
|
-
this.logger.ci(`Installing Unity ${unityVersion.toString()}...`);
|
|
1251
|
-
const hubInstallDir = await this.GetInstallPath();
|
|
1252
|
-
switch (process.platform) {
|
|
1253
|
-
case 'win32': {
|
|
1254
|
-
const installDir = path.join(hubInstallDir, `Unity ${unityVersion.version}`);
|
|
1255
|
-
const installPath = path.join(installDir, 'Unity.exe');
|
|
1256
|
-
if (!fs.existsSync(installPath)) {
|
|
1257
|
-
const url = `https://beta.unity3d.com/download/UnitySetup-${unityVersion.version}.exe`;
|
|
1258
|
-
const installerPath = path.join((0, utilities_1.GetTempDir)(), `UnitySetup-${unityVersion.version}.exe`);
|
|
1259
|
-
await (0, utilities_1.DownloadFile)(url, installerPath);
|
|
1260
|
-
this.logger.info(`Running Unity ${unityVersion.toString()} installer...`);
|
|
1261
|
-
try {
|
|
1262
|
-
await (0, utilities_1.Exec)('powershell', [
|
|
1263
|
-
'-Command',
|
|
1264
|
-
`Start-Process -FilePath \"${installerPath}\" -ArgumentList \"/S /D=${installDir}\" -Wait`
|
|
1265
|
-
], { silent: true, showCommand: true });
|
|
1266
|
-
}
|
|
1267
|
-
catch (error) {
|
|
1268
|
-
this.logger.error(`Failed to install Unity ${unityVersion.toString()}: ${error}`);
|
|
1269
|
-
}
|
|
1270
|
-
finally {
|
|
1271
|
-
fs.promises.unlink(installerPath);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
await fs.promises.access(installDir, fs.constants.R_OK | fs.constants.X_OK);
|
|
1275
|
-
return installDir;
|
|
1276
|
-
}
|
|
1277
|
-
case 'darwin': {
|
|
1278
|
-
const installDir = path.join(hubInstallDir, `Unity ${unityVersion.version}`, 'Unity.app');
|
|
1279
|
-
if (!fs.existsSync(installDir)) {
|
|
1280
|
-
const url = `https://beta.unity3d.com/download/unity-${unityVersion.version}.dmg`;
|
|
1281
|
-
const installerPath = path.join((0, utilities_1.GetTempDir)(), `UnitySetup-${unityVersion.version}.dmg`);
|
|
1282
|
-
await (0, utilities_1.DownloadFile)(url, installerPath);
|
|
1283
|
-
this.logger.info(`Running Unity ${unityVersion.toString()} installer...`);
|
|
1284
|
-
let mountPoint = '';
|
|
1285
|
-
try {
|
|
1286
|
-
const output = await (0, utilities_1.Exec)('hdiutil', ['attach', installerPath, '-nobrowse'], { silent: true, showCommand: true });
|
|
1287
|
-
const mountPointMatch = output.match(/\/Volumes\/Unity Installer.*$/m);
|
|
1288
|
-
if (!mountPointMatch || mountPointMatch.length === 0) {
|
|
1289
|
-
throw new Error(`Failed to find mount point in hdiutil output: ${output}`);
|
|
1290
|
-
}
|
|
1291
|
-
mountPoint = mountPointMatch[0];
|
|
1292
|
-
this.logger.debug(`Mounted Unity Installer at ${mountPoint}`);
|
|
1293
|
-
const pkgPath = path.join(mountPoint, 'Unity.pkg');
|
|
1294
|
-
await fs.promises.access(pkgPath, fs.constants.R_OK);
|
|
1295
|
-
this.logger.debug(`Found .pkg installer: ${pkgPath}`);
|
|
1296
|
-
await (0, utilities_1.Exec)('sudo', ['installer', '-pkg', pkgPath, '-target', '/', '-verboseR'], { silent: true, showCommand: true });
|
|
1297
|
-
const unityAppPath = path.join('/Applications', 'Unity');
|
|
1298
|
-
const targetPath = path.join(hubInstallDir, `Unity ${unityVersion.version}`);
|
|
1299
|
-
if (fs.existsSync(unityAppPath)) {
|
|
1300
|
-
this.logger.debug(`Moving ${unityAppPath} to ${targetPath}...`);
|
|
1301
|
-
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
1302
|
-
const items = await fs.promises.readdir(unityAppPath);
|
|
1303
|
-
for (const item of items) {
|
|
1304
|
-
if (item !== 'Hub') {
|
|
1305
|
-
const src = path.join(unityAppPath, item);
|
|
1306
|
-
const dest = path.join(targetPath, item);
|
|
1307
|
-
this.logger.debug(` > Moving ${src} to ${dest}`);
|
|
1308
|
-
await fs.promises.cp(src, dest, { recursive: true });
|
|
1309
|
-
await fs.promises.rm(src, { recursive: true, force: true });
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
await fs.promises.chmod(targetPath, 0o777);
|
|
1313
|
-
}
|
|
1314
|
-
else {
|
|
1315
|
-
throw new Error(`Failed to find Unity.app after installation.`);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
catch (error) {
|
|
1319
|
-
this.logger.error(`Failed to mount Unity ${unityVersion.toString()} installer: ${error}`);
|
|
1320
|
-
}
|
|
1321
|
-
finally {
|
|
1322
|
-
try {
|
|
1323
|
-
if (mountPoint && mountPoint.length > 0) {
|
|
1324
|
-
await (0, utilities_1.Exec)('hdiutil', ['detach', mountPoint, '-quiet'], { silent: true, showCommand: true });
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
finally {
|
|
1328
|
-
await fs.promises.unlink(installerPath);
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
await fs.promises.access(installDir, fs.constants.R_OK | fs.constants.X_OK);
|
|
1333
|
-
return installDir;
|
|
1334
|
-
}
|
|
1335
|
-
default:
|
|
1336
|
-
throw new Error(`Unity ${unityVersion.toString()} is not supported on ${process.platform}`);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
/**
|
|
1340
|
-
* Get the mapping of Unity platform targets to their corresponding module identifiers for the current OS.
|
|
1341
|
-
* @returns A map of Unity platform targets to their corresponding module identifiers for the current OS.
|
|
1342
|
-
*/
|
|
1343
|
-
static GetPlatformTargetModuleMap() {
|
|
1344
|
-
const osType = os.type();
|
|
1345
|
-
let moduleMap;
|
|
1346
|
-
switch (osType) {
|
|
1347
|
-
case 'Linux':
|
|
1348
|
-
moduleMap = {
|
|
1349
|
-
"StandaloneLinux64": "linux-il2cpp",
|
|
1350
|
-
"Android": "android",
|
|
1351
|
-
"WebGL": "webgl",
|
|
1352
|
-
"iOS": "ios",
|
|
1353
|
-
};
|
|
1354
|
-
break;
|
|
1355
|
-
case 'Darwin':
|
|
1356
|
-
moduleMap = {
|
|
1357
|
-
"StandaloneOSX": "mac-il2cpp",
|
|
1358
|
-
"iOS": "ios",
|
|
1359
|
-
"Android": "android",
|
|
1360
|
-
"tvOS": "appletv",
|
|
1361
|
-
"StandaloneLinux64": "linux-il2cpp",
|
|
1362
|
-
"WebGL": "webgl",
|
|
1363
|
-
"VisionOS": "visionos"
|
|
1364
|
-
};
|
|
1365
|
-
break;
|
|
1366
|
-
case 'Windows_NT':
|
|
1367
|
-
moduleMap = {
|
|
1368
|
-
"StandaloneWindows64": "windows-il2cpp",
|
|
1369
|
-
"WSAPlayer": "universal-windows-platform",
|
|
1370
|
-
"Android": "android",
|
|
1371
|
-
"iOS": "ios",
|
|
1372
|
-
"tvOS": "appletv",
|
|
1373
|
-
"StandaloneLinux64": "linux-il2cpp",
|
|
1374
|
-
"Lumin": "lumin",
|
|
1375
|
-
"WebGL": "webgl",
|
|
1376
|
-
};
|
|
1377
|
-
break;
|
|
1378
|
-
default:
|
|
1379
|
-
throw Error(`${osType} not supported`);
|
|
1380
|
-
}
|
|
1381
|
-
return moduleMap;
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Returns the path to the Unity Hub log file.
|
|
1385
|
-
* @see https://docs.unity.com/en-us/licensing-server/troubleshooting-client#logs
|
|
1386
|
-
* @returns The Unity Hub log file path.
|
|
1387
|
-
*/
|
|
1388
|
-
static LogPath() {
|
|
1389
|
-
switch (process.platform) {
|
|
1390
|
-
case 'win32':
|
|
1391
|
-
// %APPDATA%\UnityHub\logs\info-log.json
|
|
1392
|
-
return path.join(process.env.APPDATA || '', 'UnityHub', 'logs', 'info-log.json');
|
|
1393
|
-
case 'darwin':
|
|
1394
|
-
// ~/Library/Application Support/UnityHub/logs/info-log.json
|
|
1395
|
-
return path.join(process.env.HOME || '', 'Library', 'Application Support', 'UnityHub', 'logs', 'info-log.json');
|
|
1396
|
-
case 'linux':
|
|
1397
|
-
// ~/.config/UnityHub/logs/info-log.json
|
|
1398
|
-
return path.join(process.env.HOME || '', '.config', 'UnityHub', 'logs', 'info-log.json');
|
|
1399
|
-
default:
|
|
1400
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
/**
|
|
1404
|
-
* Returns the path to the Unity Package Manager log file.
|
|
1405
|
-
* @see https://docs.unity3d.com/Manual/LogFiles.html
|
|
1406
|
-
* @returns The Unity Package Manager log file path.
|
|
1407
|
-
*/
|
|
1408
|
-
static PackageManagerLogsPath() {
|
|
1409
|
-
switch (process.platform) {
|
|
1410
|
-
case 'win32':
|
|
1411
|
-
// C:\Users\username\AppData\Local\Unity\Editor\upm.log
|
|
1412
|
-
return path.join(process.env.LOCALAPPDATA || '', 'Unity', 'Editor', 'upm.log');
|
|
1413
|
-
case 'darwin':
|
|
1414
|
-
// ~/Library/Logs/Unity/upm.log
|
|
1415
|
-
return path.join(process.env.HOME || '', 'Library', 'Logs', 'Unity', 'upm.log');
|
|
1416
|
-
case 'linux':
|
|
1417
|
-
// ~/.config/unity3d/upm.log
|
|
1418
|
-
return path.join(process.env.HOME || '', '.config', 'unity3d', 'upm.log');
|
|
1419
|
-
default:
|
|
1420
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
exports.UnityHub = UnityHub;
|
|
1425
|
-
//# sourceMappingURL=unity-hub.js.map
|