From 33c2ccf664cd1a09a82b302bcd1def9f76d067d8 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 14:45:23 -0400 Subject: [PATCH 01/26] unity-cli@v1.2.1 - hotfix to install libssl 1.1.1 for 2021.x editors --- package-lock.json | 4 ++-- package.json | 2 +- src/license-client.ts | 2 +- src/unity-hub.ts | 6 +++++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c8dda24d..8e92fa0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.2.0", + "version": "1.2.1", "license": "MIT", "dependencies": { "@electron/asar": "^4.0.1", diff --git a/package.json b/package.json index 7c6eda3f..8b18f73a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.2.0", + "version": "1.2.1", "description": "A command line utility for the Unity Game Engine.", "author": "RageAgainstThePixel", "license": "MIT", diff --git a/src/license-client.ts b/src/license-client.ts index 28eaaf1c..d64b47e7 100644 --- a/src/license-client.ts +++ b/src/license-client.ts @@ -198,7 +198,7 @@ export class LicensingClient { const patchedDirectory = path.join(os.tmpdir(), `UnityLicensingClient-${this.licenseVersion.replace('.', '_')}`); if (await fs.promises.mkdir(patchedDirectory, { recursive: true }) === undefined) { - this.logger.info('Unity Licensing Client was already patched, reusing'); + this.logger.debug('Unity Licensing Client was already patched, reusing'); } else { let found = false; for (const fileName of await fs.promises.readdir(clientDirectory)) { diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 5460067d..e2f5e0fc 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -960,7 +960,11 @@ done } finally { fs.promises.unlink(downloadPath); } - } else if (['2019.3', '2019.4'].some(v => unityVersion.version.startsWith(v)) || unityVersion.version.startsWith('2020.')) { + } else if ( + ['2019.3', '2019.4'].some(v => unityVersion.version.startsWith(v)) || + unityVersion.version.startsWith('2020.') || + unityVersion.version.startsWith('2021.') + ) { const url = `https://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.0g-2ubuntu4_${arch}.deb`; const downloadPath = path.join(GetTempDir(), `libssl1.1_1.1.0g-2ubuntu4_${arch}.deb`); await DownloadFile(url, downloadPath); From 4086db170522262c98c6641cf413d3d64b49b834 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 14:48:27 -0400 Subject: [PATCH 02/26] test all the things --- .github/workflows/build-options.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index eccaad9e..bb639539 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -5,7 +5,13 @@ "macos-latest" ], "unity-version": [ - "2022.3.x", + "4.7.2", + "5.6.7f1 (e80cc3114ac1)", + "2017.x", + "2018.x", + "2019.x", + "2021.x", + "2022.x", "6000.0.x", "6000.1.x", "6000.2.x" @@ -23,5 +29,19 @@ "os": "macos-latest", "build-target": "StandaloneOSX" } + ], + "exclude": [ + { + "os": "ubuntu-latest", + "unity-version": "4.7.2" + }, + { + "os": "macos-latest", + "unity-version": "4.7.2" + }, + { + "os": "ubuntu-latest", + "unity-version": "5.6.7f1 (e80cc3114ac1)" + } ] } \ No newline at end of file From ab8fc54b612f70cb8dd4714dd3527e0717f80c5a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 15:05:05 -0400 Subject: [PATCH 03/26] revert build options --- .github/workflows/build-options.json | 19 ------------------- src/cli.ts | 1 - 2 files changed, 20 deletions(-) diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index bb639539..170c817f 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -5,11 +5,6 @@ "macos-latest" ], "unity-version": [ - "4.7.2", - "5.6.7f1 (e80cc3114ac1)", - "2017.x", - "2018.x", - "2019.x", "2021.x", "2022.x", "6000.0.x", @@ -29,19 +24,5 @@ "os": "macos-latest", "build-target": "StandaloneOSX" } - ], - "exclude": [ - { - "os": "ubuntu-latest", - "unity-version": "4.7.2" - }, - { - "os": "macos-latest", - "unity-version": "4.7.2" - }, - { - "os": "ubuntu-latest", - "unity-version": "5.6.7f1 (e80cc3114ac1)" - } ] } \ No newline at end of file diff --git a/src/cli.ts b/src/cli.ts index 312ecc57..fbe8ee14 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -269,7 +269,6 @@ program.command('setup-unity') program.commandsGroup('Unity Editor:'); - program.command('run') .description('Run command line args directly to the Unity Editor.') .option('--unity-editor ', 'The path to the Unity Editor executable. If unspecified, the UNITY_EDITOR_PATH environment variable must be set.') From 499166df3505c664a1dfc26d139247fcc4a50302 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 15:44:19 -0400 Subject: [PATCH 04/26] rework all of the cli arg documentation made commands simpler --- src/cli.ts | 124 +++++++++++++++++++++++++++++++++++++---------- src/unity-hub.ts | 2 +- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index fbe8ee14..9f848183 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,19 +3,20 @@ import 'source-map-support/register'; import * as fs from 'fs'; import * as os from 'os'; +import * as path from 'path'; import { Command } from 'commander'; -import path, { join } from 'path'; -import { LicenseType, LicensingClient } from './license-client'; -import { PromptForSecretInput } from './utilities'; import { UnityHub } from './unity-hub'; +import updateNotifier from "update-notifier"; import { Logger, LogLevel } from './logging'; +import { UnityEditor } from './unity-editor'; import { UnityVersion } from './unity-version'; import { UnityProject } from './unity-project'; +import { ChildProcess, spawn } from 'child_process'; +import { PromptForSecretInput } from './utilities'; import { CheckAndroidSdkInstalled } from './android-sdk'; -import { UnityEditor } from './unity-editor'; -import updateNotifier from "update-notifier"; +import { LicenseType, LicensingClient } from './license-client'; -const pkgPath = join(__dirname, '..', 'package.json'); +const pkgPath = path.join(__dirname, '..', 'package.json'); const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); updateNotifier({ pkg }).notify(); const program = new Command(); @@ -205,7 +206,7 @@ program.command('setup-unity') } if (!options.unityVersion && !unityProject) { - throw new Error('You must specify a Unity version or project with -u, --unity-version, -p, --unity-project.'); + throw new Error('You must specify a Unity version or project path with -u, --unity-version, -p, --unity-project.'); } const unityVersion = unityProject?.version ?? new UnityVersion(options.unityVersion, options.changeset); @@ -269,10 +270,43 @@ program.command('setup-unity') program.commandsGroup('Unity Editor:'); +program.command('open-project') + .description('Open a Unity project in the Unity Editor.') + .option('-p, --unity-project ', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.') + .option('-u, --unity-version ', 'The Unity version to get (e.g. 2020.3.1f1, 2021.x, 2022.1.*, 6000). If specified, it will override the version read from the project.') + .option('--verbose', 'Enable verbose logging.') + .action(async (options) => { + if (options.verbose) { + Logger.instance.logLevel = LogLevel.DEBUG; + } + + Logger.instance.debug(JSON.stringify(options)); + const projectPath = options.unityProject?.toString()?.trim() || process.env.UNITY_PROJECT_PATH || process.cwd(); + const unityProject = await UnityProject.GetProject(projectPath); + + if (!unityProject) { + throw new Error(`The specified path is not a valid Unity project: ${projectPath}`); + } + + const unityVersion = unityProject?.version ?? new UnityVersion(options.unityVersion, options.changeset); + const unityHub = new UnityHub(); + const unityEditor = await unityHub.GetEditor(unityVersion); + + Logger.instance.info(`Opening project at "${unityProject.projectPath}" with Unity ${unityEditor.version}...`); + + let child: ChildProcess | null = null; + try { + child = spawn(unityEditor.editorPath, ['-projectPath', unityProject.projectPath], { detached: true }); + child.unref(); + } finally { + process.exit(child?.pid !== undefined ? 0 : 1); + } + }); + program.command('run') .description('Run command line args directly to the Unity Editor.') - .option('--unity-editor ', 'The path to the Unity Editor executable. If unspecified, the UNITY_EDITOR_PATH environment variable must be set.') - .option('--unity-project ', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.') + .option('-e, --unity-editor ', 'The path to the Unity Editor executable. If unspecified, -u, --unity-project or the UNITY_EDITOR_PATH environment variable must be set.') + .option('-p, --unity-project ', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.') .option('--log-name ', 'The name of the log file.') .allowUnknownOption(true) .argument('', 'Arguments to pass to the Unity Editor executable.') @@ -284,13 +318,13 @@ program.command('run') Logger.instance.debug(JSON.stringify({ options, args })); - const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH; + let unityEditor: UnityEditor | undefined; + const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH || undefined; - if (!editorPath || editorPath.length === 0) { - throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR_PATH environment variable.'); + if (editorPath && editorPath.length > 0) { + unityEditor = new UnityEditor(editorPath); } - const unityEditor = new UnityEditor(editorPath); const projectPath = options.unityProject?.toString()?.trim() || process.env.UNITY_PROJECT_PATH || process.cwd(); const unityProject = await UnityProject.GetProject(projectPath); @@ -298,6 +332,15 @@ program.command('run') throw new Error(`The specified path is not a valid Unity project: ${projectPath}`); } + if (!unityEditor) { + const unityHub = new UnityHub(); + unityEditor = await unityHub.GetEditor(unityProject.version); + } + + if (!unityEditor) { + throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR_PATH environment variable.'); + } + if (!args.includes('-logFile')) { const logPath = unityEditor.GenerateLogFilePath(unityProject.projectPath, options.logName); args.push('-logFile', logPath); @@ -310,7 +353,8 @@ program.command('run') program.command('list-project-templates') .description('List all available project templates for the given Unity editor.') - .option('--unity-editor ', 'The path to the Unity Editor executable. If unspecified, the UNITY_EDITOR_PATH environment variable must be set.') + .option('-u, --unity-version ', 'The Unity version to get (e.g. 2020.3.1f1, 2021.x, 2022.1.*, 6000). If unspecified, then --unity-editor must be specified.') + .option('-e, --unity-editor ', 'The path to the Unity Editor executable. If unspecified, the UNITY_EDITOR_PATH environment variable must be set.') .option('--verbose', 'Enable verbose logging.') .option('--json', 'Prints the last line of output as JSON string.') .action(async (options) => { @@ -320,13 +364,27 @@ program.command('list-project-templates') Logger.instance.debug(JSON.stringify(options)); - const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH; + const unityVersionStr = options.unityVersion?.toString()?.trim(); - if (!editorPath || editorPath.length === 0) { - throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR_PATH environment variable.'); + if (!unityVersionStr && !options.unityEditor) { + throw new Error('You must specify a Unity version or editor path with -u, --unity-version, -e, --unity-editor.'); + } + + let unityEditor: UnityEditor; + + if (unityVersionStr) { + const unityVersion = new UnityVersion(unityVersionStr); + unityEditor = await new UnityHub().GetEditor(unityVersion); + } else { + const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH; + + if (!editorPath || editorPath.length === 0) { + throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR_PATH environment variable.'); + } + + unityEditor = new UnityEditor(editorPath); } - const unityEditor = new UnityEditor(editorPath); const templates = unityEditor.GetAvailableTemplates(); if (options.json) { @@ -341,10 +399,11 @@ program.command('list-project-templates') program.command('create-project') .description('Create a new Unity project.') - .option('--name ', 'The name of the new Unity project. If unspecified, the project will be created in the specified path or the current working directory.') - .option('--path ', 'The path to create the new Unity project. If unspecified, the current working directory will be used.') - .option('--template ', 'The name of the template package to use for creating the unity project. Supports regex patterns.', 'com.unity.template.3d(-cross-platform)?') - .option('--unity-editor ', 'The path to the Unity Editor executable. If unspecified, the UNITY_EDITOR_PATH environment variable must be set.') + .option('-n, --name ', 'The name of the new Unity project. If unspecified, the project will be created in the specified path or the current working directory.') + .option('-p, --path ', 'The path to create the new Unity project. If unspecified, the current working directory will be used.') + .option('-t, --template ', 'The name of the template package to use for creating the unity project. Supports regex patterns.', 'com.unity.template.3d(-cross-platform)?') + .option('-u, --unity-version ', 'The Unity version to get (e.g. 2020.3.1f1, 2021.x, 2022.1.*, 6000). If unspecified, then --unity-editor must be specified.') + .option('-e, --unity-editor ', 'The path to the Unity Editor executable. If unspecified, -u, --unity-version, or the UNITY_EDITOR_PATH environment variable must be set.') .option('--verbose', 'Enable verbose logging.') .option('--json', 'Prints the last line of output as JSON string.') .action(async (options) => { @@ -354,13 +413,26 @@ program.command('create-project') Logger.instance.debug(JSON.stringify(options)); - const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH; + const unityVersionStr = options.unityVersion?.toString()?.trim(); - if (!editorPath || editorPath.length === 0) { - throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR_PATH environment variable.'); + if (!unityVersionStr && !options.unityEditor) { + throw new Error('You must specify a Unity version or editor path with -u, --unity-version, -e, --unity-editor.'); } - const unityEditor = new UnityEditor(editorPath); + let unityEditor: UnityEditor; + + if (unityVersionStr) { + const unityVersion = new UnityVersion(unityVersionStr); + unityEditor = await new UnityHub().GetEditor(unityVersion); + } else { + const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH; + + if (!editorPath || editorPath.length === 0) { + throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR_PATH environment variable.'); + } + + unityEditor = new UnityEditor(editorPath); + } if (!options.template || options.template.length === 0) { throw new Error('The project template name was not specified. Use -t or --template to specify it.'); diff --git a/src/unity-hub.ts b/src/unity-hub.ts index e2f5e0fc..cb12b310 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -516,7 +516,7 @@ chmod -R 777 "$hubPath"`]); * @param modules The modules to install alongside the editor. * @returns The path to the Unity Editor executable. */ - public async GetEditor(unityVersion: UnityVersion, modules: string[]): Promise { + public async GetEditor(unityVersion: UnityVersion, modules: string[] = []): Promise { const retryErrorMessages = [ 'Editor already installed in this location', 'failed to download. Error given: Request timeout' From c8bfd19b641888e84594cf5f1826f6961dfff064 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 15:49:08 -0400 Subject: [PATCH 05/26] test 4.7.2, 2020.x --- .github/workflows/build-options.json | 6 ++++++ src/unity-hub.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index 170c817f..85027b00 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -5,6 +5,7 @@ "macos-latest" ], "unity-version": [ + "2020.x", "2021.x", "2022.x", "6000.0.x", @@ -23,6 +24,11 @@ { "os": "macos-latest", "build-target": "StandaloneOSX" + }, + { + "os": "windows-latest", + "build-target": "StandaloneWindows64", + "unity-version": "4.7.2" } ] } \ No newline at end of file diff --git a/src/unity-hub.ts b/src/unity-hub.ts index cb12b310..ae38ca4f 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -1021,7 +1021,7 @@ done await Exec('powershell', [ '-NoProfile', '-Command', - `Start-Process -FilePath \"${installerPath}\" -ArgumentList \"/S /D=${installPath}\" -Wait -NoNewWindow -Verb RunAs` + `Start-Process -FilePath \"${installerPath}\" -ArgumentList \"/S /D=${installPath}\" -Wait -NoNewWindow` ], { silent: true, showCommand: true }); } catch (error) { this.logger.error(`Failed to install Unity ${unityVersion.toString()}: ${error}`); From 3b7272e08827daed7a2d718dd0f4c8a7cb17e24d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 15:50:42 -0400 Subject: [PATCH 06/26] update build-options --- .github/workflows/build-options.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index 85027b00..ad7bae7b 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -5,6 +5,7 @@ "macos-latest" ], "unity-version": [ + "4.7.2", "2020.x", "2021.x", "2022.x", @@ -24,10 +25,15 @@ { "os": "macos-latest", "build-target": "StandaloneOSX" + } + ], + "exclude": [ + { + "os": "ubuntu-latest", + "unity-version": "4.7.2" }, { - "os": "windows-latest", - "build-target": "StandaloneWindows64", + "os": "macos-latest", "unity-version": "4.7.2" } ] From c5d4f6ca7c0d93d4a4e0421b89992a1b15351d88 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 16:02:59 -0400 Subject: [PATCH 07/26] add uninstall option --- .github/workflows/unity-build.yml | 5 +++++ src/cli.ts | 36 +++++++++++++++++++++++++++++++ src/unity-editor.ts | 18 ++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 808f51e0..6ffc4924 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -79,6 +79,11 @@ jobs: run: | unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Validate -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Build -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity + - name: Uninstall editor + if: always() + shell: bash + run: | + unity-cli uninstall-unity --unity-editor "${UNITY_EDITOR_PATH}" - name: Post Run if: always() shell: bash diff --git a/src/cli.ts b/src/cli.ts index 9f848183..29045e06 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -268,6 +268,42 @@ program.command('setup-unity') } }); +program.command('uninstall-unity') + .description('Uninstall the specified Unity Editor version.') + .option('-e, --unity-editor ', 'The path to the Unity Editor executable. If unspecified, -u, --unity-version or the UNITY_EDITOR_PATH environment variable must be set.') + .option('-u, --unity-version ', 'The Unity version to get (e.g. 2020.3.1f1, 2021.x, 2022.1.*, 6000). If unspecified, then --unity-editor must be specified.') + .option('-c, --changeset ', 'The Unity changeset to get (e.g. 1234567890ab).') + .option('-a, --arch ', 'The Unity architecture to get (e.g. x86_64, arm64). Defaults to the architecture of the current process.') + .option('--verbose', 'Enable verbose logging.') + .action(async (options) => { + if (options.verbose) { + Logger.instance.logLevel = LogLevel.DEBUG; + } + + Logger.instance.debug(JSON.stringify(options)); + + let unityEditor: UnityEditor | undefined; + const unityVersionStr = options.unityVersion?.toString()?.trim(); + + if (unityVersionStr) { + const unityVersion = new UnityVersion(unityVersionStr, options.changeset, options.arch); + const unityHub = new UnityHub(); + unityEditor = await unityHub.GetEditor(unityVersion); + } else { + const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH || undefined; + if (!editorPath || editorPath.length === 0) { + throw new Error('You must specify a Unity version or editor path with -u, --unity-version, -e, --unity-editor.'); + } + unityEditor = new UnityEditor(editorPath); + } + + if (!unityEditor) { + throw new Error('The Unity Editor could not be found.'); + } + + await unityEditor.Uninstall(); + }); + program.commandsGroup('Unity Editor:'); program.command('open-project') diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 7f9ec4c1..c057f9d5 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -14,6 +14,7 @@ import { TailLogFile, LogTailResult, WaitForFileToBeCreatedAndReadable, + Exec, } from './utilities'; export interface EditorCommand { @@ -316,4 +317,21 @@ export class UnityEditor { fs.accessSync(editorRootPath, fs.constants.R_OK); return editorRootPath; } + + public async Uninstall(): Promise { + switch (process.platform) { + case 'darwin': + case 'linux': + await Exec('sudo', ['rm', '-rf', this.editorRootPath], { silent: true, showCommand: true }); + break; + case 'win32': + const uninstallPath = path.join(this.editorRootPath, 'Uninstall.exe'); + await Exec('powershell', [ + '-Command', + `Start-Process -File "${uninstallPath}" -ArgumentList "/S" -NoNewWindow -Wait -Verb RunAs` + ], { silent: true, showCommand: true }); + break; + } + + } } \ No newline at end of file From 95225ab0e72e8867a3ed73ce9c525ba92788a42e Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 16:07:45 -0400 Subject: [PATCH 08/26] clean up some cli args and options --- src/cli.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 29045e06..4978bec8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -167,7 +167,6 @@ program.command('hub-path') program.command('hub') .description('Run commands directly to the Unity Hub. (You need not to pass --headless or -- to this command).') - .allowUnknownOption(true) .argument('', 'Arguments to pass to the Unity Hub executable.') .option('--verbose', 'Enable verbose logging.') .action(async (args: string[], options) => { @@ -341,12 +340,11 @@ program.command('open-project') program.command('run') .description('Run command line args directly to the Unity Editor.') - .option('-e, --unity-editor ', 'The path to the Unity Editor executable. If unspecified, -u, --unity-project or the UNITY_EDITOR_PATH environment variable must be set.') - .option('-p, --unity-project ', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.') + .option('--unity-editor ', 'The path to the Unity Editor executable. If unspecified, --unity-project or the UNITY_EDITOR_PATH environment variable must be set.') + .option('--unity-project ', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.') .option('--log-name ', 'The name of the log file.') - .allowUnknownOption(true) - .argument('', 'Arguments to pass to the Unity Editor executable.') .option('--verbose', 'Enable verbose logging.') + .argument('', 'Arguments to pass to the Unity Editor executable.') .action(async (args: string[], options) => { if (options.verbose) { Logger.instance.logLevel = LogLevel.DEBUG; @@ -374,7 +372,7 @@ program.command('run') } if (!unityEditor) { - throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR_PATH environment variable.'); + throw new Error('The Unity Editor path was not specified. Use --unity-editor to specify it or set the UNITY_EDITOR_PATH environment variable.'); } if (!args.includes('-logFile')) { From bbf0e86ac50fd40d2538ff1d827a227c9bed1e71 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 16:11:38 -0400 Subject: [PATCH 09/26] UNITY_EDITOR_PATH --- .github/workflows/unity-build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 6ffc4924..f4373787 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -22,6 +22,8 @@ jobs: permissions: contents: read timeout-minutes: 30 + env: + UNITY_PROJECT_PATH: '' # Set from create-project step steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -80,11 +82,11 @@ jobs: unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Validate -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Build -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity - name: Uninstall editor - if: always() + if: ${{ always() && env.UNITY_EDITOR_PATH != '' }} shell: bash run: | unity-cli uninstall-unity --unity-editor "${UNITY_EDITOR_PATH}" - - name: Post Run + - name: Return license if: always() shell: bash run: | From f7eb95367c4ac531a7d0a1f0bb592df02bb07ad6 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 16:48:26 -0400 Subject: [PATCH 10/26] add uninstall-unity command line option --- src/cli.ts | 23 +++++++++++++++++++---- src/unity-editor.ts | 28 ++++++++++++++++++++++++---- src/unity-hub.ts | 42 +++++++++++++++++++++--------------------- src/unity-version.ts | 4 ++++ 4 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 4978bec8..e55805da 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -287,20 +287,35 @@ program.command('uninstall-unity') if (unityVersionStr) { const unityVersion = new UnityVersion(unityVersionStr, options.changeset, options.arch); const unityHub = new UnityHub(); - unityEditor = await unityHub.GetEditor(unityVersion); + const installedEditors = await unityHub.ListInstalledEditors(); + if (unityVersion.isLegacy()) { + const installPath = await unityHub.GetInstallPath(); + unityEditor = new UnityEditor(path.join(installPath, `Unity ${unityVersion.toString()}`, 'Unity.exe')); + } else { + unityEditor = installedEditors.find(e => e.version.equals(unityVersion)); + } } else { const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH || undefined; + if (!editorPath || editorPath.length === 0) { throw new Error('You must specify a Unity version or editor path with -u, --unity-version, -e, --unity-editor.'); } - unityEditor = new UnityEditor(editorPath); + + try { + unityEditor = new UnityEditor(editorPath); + } catch { + // ignored + } } if (!unityEditor) { - throw new Error('The Unity Editor could not be found.'); + Logger.instance.info('The specified Unity Editor was not found.'); + } + else { + await unityEditor.Uninstall(); } - await unityEditor.Uninstall(); + process.exit(0); }); program.commandsGroup('Unity Editor:'); diff --git a/src/unity-editor.ts b/src/unity-editor.ts index c057f9d5..c3e33e93 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -322,16 +322,36 @@ export class UnityEditor { switch (process.platform) { case 'darwin': case 'linux': - await Exec('sudo', ['rm', '-rf', this.editorRootPath], { silent: true, showCommand: true }); + await Exec('sudo', [ + 'rm', '-rf', this.editorRootPath + ], { silent: true, showCommand: true }); break; case 'win32': - const uninstallPath = path.join(this.editorRootPath, 'Uninstall.exe'); + const uninstallPath = path.join(path.dirname(this.editorPath), 'Uninstall.exe'); + await fs.promises.access(uninstallPath, fs.constants.R_OK | fs.constants.X_OK); await Exec('powershell', [ + '-NoProfile', '-Command', - `Start-Process -File "${uninstallPath}" -ArgumentList "/S" -NoNewWindow -Wait -Verb RunAs` + `Start-Process -FilePath "${uninstallPath}" -ArgumentList "/S" -Wait` ], { silent: true, showCommand: true }); + // also delete the editor root directory if it still exists + if (fs.existsSync(this.editorRootPath)) { + await Exec('powershell', [ + '-NoProfile', + '-Command', + `Remove-Item -Path "${this.editorRootPath}" -Recurse -Force` + ], { silent: true, showCommand: true }); + } + // also delete the MonoBehaviour directory one level up if it still exists + const monoBehaviourPath = path.join(path.dirname(this.editorRootPath), 'MonoBehaviour'); + if (fs.existsSync(monoBehaviourPath)) { + await Exec('powershell', [ + '-NoProfile', + '-Command', + `Remove-Item -Path "${monoBehaviourPath}" -Recurse -Force` + ], { silent: true, showCommand: true }); + } break; } - } } \ No newline at end of file diff --git a/src/unity-hub.ts b/src/unity-hub.ts index ae38ca4f..1f503fa3 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -550,28 +550,28 @@ chmod -R 777 "$hubPath"`]); let editorPath = await this.checkInstalledEditors(resolvedVersion, false, undefined, allowPartialMatches); unityVersion = resolvedVersion; - let installPath: string | undefined = undefined; + let installDir: string | undefined = undefined; if (!editorPath) { try { - installPath = await this.installUnity(unityVersion, modules); + installDir = await this.installUnity(unityVersion, modules); } catch (error: Error | any) { if (retryErrorMessages.some(msg => error.message.includes(msg))) { if (editorPath) { await DeleteDirectory(editorPath); } - if (installPath) { - await DeleteDirectory(installPath); + if (installDir) { + await DeleteDirectory(installDir); } - installPath = await this.installUnity(unityVersion, modules); + installDir = await this.installUnity(unityVersion, modules); } else { throw error; } } - editorPath = await this.checkInstalledEditors(unityVersion, true, installPath); + editorPath = await this.checkInstalledEditors(unityVersion, true, installDir); } if (!editorPath) { @@ -663,12 +663,12 @@ chmod -R 777 "$hubPath"`]); private async checkInstalledEditors( unityVersion: UnityVersion, failOnEmpty: boolean, - installPath: string | undefined = undefined, + installDir: string | undefined = undefined, allowPartialMatches: boolean = true ): Promise { let editorPath = undefined; - if (!installPath) { + if (!installDir) { const editors: UnityEditor[] = await this.ListInstalledEditors(); if (editors && editors.length > 0) { @@ -713,9 +713,9 @@ chmod -R 777 "$hubPath"`]); } } else { if (process.platform == 'win32') { - editorPath = path.join(installPath, 'Unity.exe'); + editorPath = path.join(installDir, 'Unity.exe'); } else { - editorPath = installPath; + editorPath = installDir; } } @@ -1004,11 +1004,12 @@ done } private async installUnity4x(unityVersion: UnityVersion): Promise { - const installDir = await this.GetInstallPath(); + const hubInstallDir = await this.GetInstallPath(); switch (process.platform) { case 'win32': { - const installPath = path.join(installDir, `Unity ${unityVersion.version}`); + const installDir = path.join(hubInstallDir, `Unity ${unityVersion.version}`); + const installPath = path.join(installDir, 'Unity.exe'); if (!fs.existsSync(installPath)) { const url = `https://beta.unity3d.com/download/UnitySetup-${unityVersion.version}.exe`; @@ -1019,9 +1020,8 @@ done try { await Exec('powershell', [ - '-NoProfile', '-Command', - `Start-Process -FilePath \"${installerPath}\" -ArgumentList \"/S /D=${installPath}\" -Wait -NoNewWindow` + `Start-Process -FilePath \"${installerPath}\" -ArgumentList \"/S /D=${installDir}\" -Wait` ], { silent: true, showCommand: true }); } catch (error) { this.logger.error(`Failed to install Unity ${unityVersion.toString()}: ${error}`); @@ -1030,13 +1030,13 @@ done } } - await fs.promises.access(installPath, fs.constants.R_OK); - return installPath; + await fs.promises.access(installDir, fs.constants.R_OK | fs.constants.X_OK); + return installDir; } case 'darwin': { - const installPath = path.join(installDir, `Unity ${unityVersion.version}`, 'Unity.app'); + const installDir = path.join(hubInstallDir, `Unity ${unityVersion.version}`, 'Unity.app'); - if (!fs.existsSync(installPath)) { + if (!fs.existsSync(installDir)) { const url = `https://beta.unity3d.com/download/unity-${unityVersion.version}.dmg`; const installerPath = path.join(GetTempDir(), `UnitySetup-${unityVersion.version}.dmg`); await DownloadFile(url, installerPath); @@ -1062,7 +1062,7 @@ done this.logger.debug(`Found .pkg installer: ${pkgPath}`); await Exec('sudo', ['installer', '-pkg', pkgPath, '-target', '/', '-verboseR'], { silent: true, showCommand: true }); const unityAppPath = path.join('/Applications', 'Unity'); - const targetPath = path.join(installDir, `Unity ${unityVersion.version}`); + const targetPath = path.join(hubInstallDir, `Unity ${unityVersion.version}`); if (fs.existsSync(unityAppPath)) { this.logger.debug(`Moving ${unityAppPath} to ${targetPath}...`); @@ -1097,8 +1097,8 @@ done } } - await fs.promises.access(installPath, fs.constants.R_OK); - return installPath; + await fs.promises.access(installDir, fs.constants.R_OK | fs.constants.X_OK); + return installDir; } default: throw new Error(`Unity ${unityVersion.toString()} is not supported on ${process.platform}`); diff --git a/src/unity-version.ts b/src/unity-version.ts index 77a67b1c..2e28853f 100644 --- a/src/unity-version.ts +++ b/src/unity-version.ts @@ -108,6 +108,10 @@ export class UnityVersion { return satisfies(coercedVersion, `^${this.semVer.version}`); } + equals(other: UnityVersion): boolean { + return UnityVersion.compare(this, other) === 0; + } + private static readonly UNITY_RELEASE_PATTERN = /^(\d{1,4})\.(\d+)\.(\d+)([abcfpx])(\d+)$/; private static readonly VERSION_TOKEN_PATTERN = /^(\d{1,4})(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?/; From 81f8c29c435939e26d0a86536e6df438fd193653 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 16:51:48 -0400 Subject: [PATCH 11/26] rework uninstaller --- src/unity-editor.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/unity-editor.ts b/src/unity-editor.ts index c3e33e93..53e775b9 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -327,7 +327,8 @@ export class UnityEditor { ], { silent: true, showCommand: true }); break; case 'win32': - const uninstallPath = path.join(path.dirname(this.editorPath), 'Uninstall.exe'); + const editorDir = path.dirname(this.editorPath); + const uninstallPath = path.join(editorDir, 'Uninstall.exe'); await fs.promises.access(uninstallPath, fs.constants.R_OK | fs.constants.X_OK); await Exec('powershell', [ '-NoProfile', @@ -335,15 +336,15 @@ export class UnityEditor { `Start-Process -FilePath "${uninstallPath}" -ArgumentList "/S" -Wait` ], { silent: true, showCommand: true }); // also delete the editor root directory if it still exists - if (fs.existsSync(this.editorRootPath)) { + if (fs.existsSync(editorDir)) { await Exec('powershell', [ '-NoProfile', '-Command', - `Remove-Item -Path "${this.editorRootPath}" -Recurse -Force` + `Remove-Item -Path "${editorDir}" -Recurse -Force` ], { silent: true, showCommand: true }); } // also delete the MonoBehaviour directory one level up if it still exists - const monoBehaviourPath = path.join(path.dirname(this.editorRootPath), 'MonoBehaviour'); + const monoBehaviourPath = path.join(path.dirname(editorDir), 'MonoBehaviour'); if (fs.existsSync(monoBehaviourPath)) { await Exec('powershell', [ '-NoProfile', From 731aad34a48bbaad8b4cff2ae142236d3e8af81d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 17:01:04 -0400 Subject: [PATCH 12/26] update readme --- README.md | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c899f9f3..161544aa 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,29 @@ unity-cli [command] [options] ### Common Commands -- `unity-cli hub-install`: Install Unity Hub -- `unity-cli hub-version`: Print Unity Hub version -- `unity-cli hub-path`: Print Unity Hub executable path -- `unity-cli hub [options] `: Run [Unity Hub command line arguments](https://docs.unity3d.com/hub/manual/HubCLI.html) +#### Auth + +- `unity-cli license-version`: Print the Unity License Client version - `unity-cli activate-license [options]`: Activate a Unity license - `unity-cli return-license [options]`: Return a Unity license -- `unity-cli license-version`: Print Unity License Client version -- `unity-cli setup-unity [options]`: Find or install Unity Editor for a project/version -- `unity-cli create-project [options]`: Create a new Unity project from a [template](https://docs.unity3d.com/hub/manual/Templates.html) -- `unity-cli run [options] `: Run [Unity Editor Command Line Arguments](https://docs.unity3d.com/Manual/EditorCommandLineArguments.html) + +#### Unity Hub + +- `unity-cli hub-version`: Print the Unity Hub version +- `unity-cli hub-install [options]`: Install or update the Unity Hub +- `unity-cli hub-path`: Print the Unity Hub executable path +- `unity-cli hub [options] `: Run Unity Hub command line arguments (passes args directly to the hub executable) + +#### Unity Editor + +- `unity-cli setup-unity [options]`: Find or install the Unity Editor for a project or specific version +- `unity-cli uninstall-unity [options]`: Uninstall a Unity Editor version +- `unity-cli list-project-templates [options]`: List available Unity project templates for an editor +- `unity-cli create-project [options]`: Create a new Unity project from a template +- `unity-cli open-project [options]`: Open a Unity project in the Unity Editor +- `unity-cli run [options] `: Run Unity Editor command line arguments (passes args directly to the editor) + +Run `unity-cli --help` for a full list of commands and options. #### Install Unity Hub and Editor @@ -60,8 +73,16 @@ unity-cli activate-license --email --password --ser unity-cli create-project --name "MyGame" --template com.unity.template.3d(-cross-platform)? --unity-editor ``` +#### Open a project from the command line + +> [!NOTE] If you run this command in the same directory as your Unity project, you can omit the `--unity-project`, `--unity-version`, and `--unity-editor` options. + +```bash +unity-cli open-project +``` + #### Build a Project ```bash -unity-cli run --unity-editor --unity-project -quit -batchmode -executeMethod StartCommandLineBuild +unity-cli run --unity-project -quit -batchmode -executeMethod StartCommandLineBuild ``` From e6929f1453dfb70f8411d17a97e5d3723af09b21 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 17:13:46 -0400 Subject: [PATCH 13/26] skip template args if legacy when creating a new project --- src/cli.ts | 26 ++++++++++++++------------ src/unity-editor.ts | 4 ++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index e55805da..9d4f9fdb 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -487,25 +487,27 @@ program.command('create-project') throw new Error('The project template name was not specified. Use -t or --template to specify it.'); } - const templatePath = unityEditor.GetTemplatePath(options.template); - const projectName = options.name?.toString()?.trim(); + let args: string[] = [ + '-quit', + '-nographics', + '-batchmode' + ]; + const projectName = options.name?.toString()?.trim(); let projectPath = options.path?.toString()?.trim() || process.cwd(); if (projectName && projectName.length > 0) { projectPath = path.join(projectPath, projectName); } - await unityEditor.Run({ - projectPath: projectPath, - args: [ - '-quit', - '-nographics', - '-batchmode', - '-createProject', projectPath, - '-cloneFromTemplate', templatePath - ] - }); + args.push('-createProject', projectPath); + + if (!unityEditor.version.isLegacy()) { + const templatePath = unityEditor.GetTemplatePath(options.template); + args.push('-cloneFromTemplate', templatePath); + } + + await unityEditor.Run({ projectPath, args }); Logger.instance.CI_setEnvironmentVariable('UNITY_PROJECT_PATH', projectPath); diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 53e775b9..aad558de 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -340,7 +340,7 @@ export class UnityEditor { await Exec('powershell', [ '-NoProfile', '-Command', - `Remove-Item -Path "${editorDir}" -Recurse -Force` + `Remove-Item -Path "${editorDir}" -Recurse -Force -Verb RunAs` ], { silent: true, showCommand: true }); } // also delete the MonoBehaviour directory one level up if it still exists @@ -349,7 +349,7 @@ export class UnityEditor { await Exec('powershell', [ '-NoProfile', '-Command', - `Remove-Item -Path "${monoBehaviourPath}" -Recurse -Force` + `Remove-Item -Path "${monoBehaviourPath}" -Recurse -Force -Verb RunAs` ], { silent: true, showCommand: true }); } break; From cdc3c37a84490cfe52be2309e0b473af2ae4d4c8 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 17:29:08 -0400 Subject: [PATCH 14/26] update template list and uninstall --- src/cli.ts | 22 +++++++++++----------- src/unity-editor.ts | 6 ++++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 9d4f9fdb..eeb874d4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -436,13 +436,17 @@ program.command('list-project-templates') const templates = unityEditor.GetAvailableTemplates(); - if (options.json) { - process.stdout.write(`\n${JSON.stringify({ templates })}\n`); - } else { - process.stdout.write(`Available project templates:\n`); - for (const template of templates) { - process.stdout.write(` - ${path.basename(template)}\n`); + if (templates.length > 0) { + if (options.json) { + process.stdout.write(`\n${JSON.stringify({ templates })}\n`); + } else { + process.stdout.write(`Available project templates:\n`); + for (const template of templates) { + process.stdout.write(` - ${path.basename(template)}\n`); + } } + } else { + process.stdout.write('No project templates found for this Unity Editor.\n'); } }); @@ -483,10 +487,6 @@ program.command('create-project') unityEditor = new UnityEditor(editorPath); } - if (!options.template || options.template.length === 0) { - throw new Error('The project template name was not specified. Use -t or --template to specify it.'); - } - let args: string[] = [ '-quit', '-nographics', @@ -502,7 +502,7 @@ program.command('create-project') args.push('-createProject', projectPath); - if (!unityEditor.version.isLegacy()) { + if (!unityEditor.version.isLegacy() && options.template && options.template.length > 0) { const templatePath = unityEditor.GetTemplatePath(options.template); args.push('-cloneFromTemplate', templatePath); } diff --git a/src/unity-editor.ts b/src/unity-editor.ts index aad558de..b437145d 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -340,7 +340,8 @@ export class UnityEditor { await Exec('powershell', [ '-NoProfile', '-Command', - `Remove-Item -Path "${editorDir}" -Recurse -Force -Verb RunAs` + `Remove-Item -Path "${editorDir}" -Recurse -Force`, + '-Verb', 'RunAs' ], { silent: true, showCommand: true }); } // also delete the MonoBehaviour directory one level up if it still exists @@ -349,7 +350,8 @@ export class UnityEditor { await Exec('powershell', [ '-NoProfile', '-Command', - `Remove-Item -Path "${monoBehaviourPath}" -Recurse -Force -Verb RunAs` + `Remove-Item -Path "${monoBehaviourPath}" -Recurse -Force`, + '-Verb', 'RunAs' ], { silent: true, showCommand: true }); } break; From 9f25a60a16803572e9d2c1810ff25faef2668b55 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 17:55:01 -0400 Subject: [PATCH 15/26] update license client patcher --- src/license-client.ts | 48 +++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/src/license-client.ts b/src/license-client.ts index d64b47e7..a7730180 100644 --- a/src/license-client.ts +++ b/src/license-client.ts @@ -154,40 +154,34 @@ export class LicensingClient { private async patchLicenseVersion(): Promise { if (!this.licenseVersion) { // check if the UNITY_EDITOR_PATH is set. If it is, use it to determine the license version - const unityEditorPath = process.env['UNITY_EDITOR_PATH']; + const unityEditorPath = process.env.UNITY_EDITOR_PATH; + const versionMatch = unityEditorPath?.match(/(\d+)\.(\d+)\.(\d+)/); - if (unityEditorPath) { - const versionMatch = unityEditorPath.match(/(\d+)\.(\d+)\.(\d+)/); - - if (!versionMatch) { - this.licenseVersion = '6.x'; // default to 6.x if version cannot be determined - } else { - switch (versionMatch[1]) { - case '4': - this.licenseVersion = '4.x'; - break; - case '5': - this.licenseVersion = '5.x'; - break; - default: - this.licenseVersion = '6.x'; // default to 6.x for any other - break; + if (unityEditorPath && versionMatch) { + switch (versionMatch[1]) { + case '4': { + this.licenseVersion = '4.x'; + break; + } + case '5': { + this.licenseVersion = '5.x'; + break; + } + default: { + this.licenseVersion = '6.x'; // default to 6.x for any other + break; } } - } - - if (!this.licenseVersion) { + } else { this.licenseVersion = '6.x'; // default to 6.x if not set } } - if (this.licenseVersion === '6.x') { - return; - } - - if (this.licenseVersion !== '5.x' && this.licenseVersion !== '4.x') { - this.logger.warn(`Warning: Specified license version '${this.licenseVersion}' is unsupported, skipping`); - return; + if (this.licenseVersion !== '6.x') { + if (this.licenseVersion !== '5.x' && this.licenseVersion !== '4.x') { + this.logger.warn(`Warning: Specified license version '${this.licenseVersion}' is unsupported, skipping`); + return; + } } if (!this.licenseClientPath) { From 37bfa6475c5b6802ee2d85b49b3a5dbb55b197c3 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 17:58:43 -0400 Subject: [PATCH 16/26] update validation workflow --- .github/workflows/unity-build.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index f4373787..413fc004 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -40,7 +40,6 @@ jobs: shell: bash run: | unity-cli hub-install --auto-update - unity-cli activate-license --license personal --email "${{ secrets.UNITY_USERNAME }}" --password "${{ secrets.UNITY_PASSWORD }}" unity-cli setup-unity --unity-version "${{ matrix.unity-version }}" --build-targets "${{ matrix.build-target }}" --json - name: verify UNITY_HUB_PATH and UNITY_EDITOR_PATH variables shell: bash @@ -58,6 +57,10 @@ jobs: echo "Error: UNITY_EDITOR_PATH is not set" exit 1 fi + - name: activate license + shell: bash + run: | + unity-cli activate-license --license personal --username "${{ secrets.UNITY_USERNAME }}" --password "${{ secrets.UNITY_PASSWORD }}" - name: create unity project shell: bash run: | @@ -82,9 +85,12 @@ jobs: unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Validate -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Build -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity - name: Uninstall editor - if: ${{ always() && env.UNITY_EDITOR_PATH != '' }} shell: bash run: | + if [ -z "${UNITY_EDITOR_PATH}" ]; then + echo "UNITY_EDITOR_PATH is not set, skipping uninstall" + exit 0 + fi unity-cli uninstall-unity --unity-editor "${UNITY_EDITOR_PATH}" - name: Return license if: always() From 54713470e47eff5ddf5c88b45467c4ff9c08f86e Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 18:10:45 -0400 Subject: [PATCH 17/26] refactor dir deletes --- src/unity-editor.ts | 23 +++++------------------ src/utilities.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/unity-editor.ts b/src/unity-editor.ts index b437145d..610d805b 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -15,6 +15,7 @@ import { LogTailResult, WaitForFileToBeCreatedAndReadable, Exec, + DeleteDirectory, } from './utilities'; export interface EditorCommand { @@ -336,24 +337,10 @@ export class UnityEditor { `Start-Process -FilePath "${uninstallPath}" -ArgumentList "/S" -Wait` ], { silent: true, showCommand: true }); // also delete the editor root directory if it still exists - if (fs.existsSync(editorDir)) { - await Exec('powershell', [ - '-NoProfile', - '-Command', - `Remove-Item -Path "${editorDir}" -Recurse -Force`, - '-Verb', 'RunAs' - ], { silent: true, showCommand: true }); - } - // also delete the MonoBehaviour directory one level up if it still exists - const monoBehaviourPath = path.join(path.dirname(editorDir), 'MonoBehaviour'); - if (fs.existsSync(monoBehaviourPath)) { - await Exec('powershell', [ - '-NoProfile', - '-Command', - `Remove-Item -Path "${monoBehaviourPath}" -Recurse -Force`, - '-Verb', 'RunAs' - ], { silent: true, showCommand: true }); - } + await DeleteDirectory(editorDir); + // also delete the MonoDevelop directory one level up if it still exists + const monoDevelopDir = path.join(path.dirname(editorDir), 'MonoDevelop'); + await DeleteDirectory(monoDevelopDir); break; } } diff --git a/src/utilities.ts b/src/utilities.ts index 3fd0b1b1..13d47aa5 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -215,9 +215,13 @@ export async function DownloadFile(url: string, downloadPath: string): Promise { - logger.debug(`Attempting to delete directory: ${targetPath}...`); if (targetPath && targetPath.length > 0 && fs.existsSync(targetPath)) { - await fs.promises.rm(targetPath, { recursive: true, force: true, maxRetries: 2, retryDelay: 100 }); + logger.debug(`Attempting to delete directory: ${targetPath}...`); + try { + await fs.promises.rm(targetPath, { recursive: true, force: true, maxRetries: 2, retryDelay: 100 }); + } catch (error) { + logger.warn(`Failed to delete directory: ${targetPath}\n${error}`); + } } } From 8334efb82026c9ed069e10922bf9af649fb703d1 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 18:11:58 -0400 Subject: [PATCH 18/26] fix name --- .github/workflows/unity-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 413fc004..a7eecd0f 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -60,7 +60,7 @@ jobs: - name: activate license shell: bash run: | - unity-cli activate-license --license personal --username "${{ secrets.UNITY_USERNAME }}" --password "${{ secrets.UNITY_PASSWORD }}" + unity-cli activate-license --license personal --email "${{ secrets.UNITY_USERNAME }}" --password "${{ secrets.UNITY_PASSWORD }}" - name: create unity project shell: bash run: | From f26c831dfb778a1735af26959de42d92efd1fd46 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 18:21:50 -0400 Subject: [PATCH 19/26] update workflow --- .github/workflows/unity-build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index a7eecd0f..2e4e9419 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -75,15 +75,17 @@ jobs: fi - name: Install OpenUPM and build pipeline package shell: bash + if: ${{ matrix.unity-version != '4.7.2' && matrix.unity-version != '5.6.7' }} run: | npm install -g openupm-cli cd "${UNITY_PROJECT_PATH}" openupm add com.utilities.buildpipeline - name: Build project + if: ${{ matrix.unity-version != '4.7.2' && matrix.unity-version != '5.6.7' }} shell: bash run: | - unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Validate -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset - unity-cli run --unity-editor "${UNITY_EDITOR_PATH}" --unity-project "${UNITY_PROJECT_PATH}" --log-name Build -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity + unity-cli run --log-name Validate -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset + unity-cli run --log-name Build -quit -nographics -batchmode -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity - name: Uninstall editor shell: bash run: | From e325cd4d1077c7ef1cd819f38450c0b59c3bf605 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 19:40:23 -0400 Subject: [PATCH 20/26] revert --- src/license-client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/license-client.ts b/src/license-client.ts index a7730180..cfd2b863 100644 --- a/src/license-client.ts +++ b/src/license-client.ts @@ -188,6 +188,10 @@ export class LicensingClient { this.licenseClientPath = await this.init(); } + if (this.licenseVersion === '6.x') { + return; // no patching needed + } + const clientDirectory = path.dirname(this.licenseClientPath); const patchedDirectory = path.join(os.tmpdir(), `UnityLicensingClient-${this.licenseVersion.replace('.', '_')}`); From c115d3b8715ab919b63a3067b8787d5f3b560b32 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 19:40:55 -0400 Subject: [PATCH 21/26] cleanup --- src/license-client.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/license-client.ts b/src/license-client.ts index cfd2b863..fa7e3ee4 100644 --- a/src/license-client.ts +++ b/src/license-client.ts @@ -154,10 +154,9 @@ export class LicensingClient { private async patchLicenseVersion(): Promise { if (!this.licenseVersion) { // check if the UNITY_EDITOR_PATH is set. If it is, use it to determine the license version - const unityEditorPath = process.env.UNITY_EDITOR_PATH; - const versionMatch = unityEditorPath?.match(/(\d+)\.(\d+)\.(\d+)/); + const versionMatch = process.env.UNITY_EDITOR_PATH?.match(/(\d+)\.(\d+)\.(\d+)/); - if (unityEditorPath && versionMatch) { + if (versionMatch) { switch (versionMatch[1]) { case '4': { this.licenseVersion = '4.x'; From 489895bb9071f9f5b2d2443ba70ea337ee19060d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 20:04:08 -0400 Subject: [PATCH 22/26] allow unknown --- src/cli.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli.ts b/src/cli.ts index eeb874d4..6070b207 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -169,6 +169,7 @@ program.command('hub') .description('Run commands directly to the Unity Hub. (You need not to pass --headless or -- to this command).') .argument('', 'Arguments to pass to the Unity Hub executable.') .option('--verbose', 'Enable verbose logging.') + .allowUnknownOption(true) .action(async (args: string[], options) => { if (options.verbose) { Logger.instance.logLevel = LogLevel.DEBUG; @@ -359,6 +360,7 @@ program.command('run') .option('--unity-project ', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.') .option('--log-name ', 'The name of the log file.') .option('--verbose', 'Enable verbose logging.') + .allowUnknownOption(true) .argument('', 'Arguments to pass to the Unity Editor executable.') .action(async (args: string[], options) => { if (options.verbose) { From de402c82d0cdae170b0c0717a96f8632c9c3fcd7 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 20:10:53 -0400 Subject: [PATCH 23/26] update hub uninstaller for windows --- src/unity-hub.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 1f503fa3..200d29c6 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -296,10 +296,18 @@ export class UnityHub { if (latestVersion && compare(installedVersion, latestVersion) < 0) { this.logger.info(`Updating Unity Hub from ${installedVersion.version} to ${latestVersion.version}...`); - if (process.platform !== 'linux') { - await DeleteDirectory(this.rootDirectory); + if (process.platform === 'darwin') { + await Exec('sudo', ['rm', '-rf', this.rootDirectory], { silent: true, showCommand: true }); await this.installHub(); - } else { + } else if (process.platform === 'win32') { + const uninstaller = path.join(path.dirname(this.executable), 'Uninstall Unity Hub.exe'); + await Exec('powershell', [ + '-NoProfile', + '-Command', + `Start-Process -FilePath '${uninstaller}' -ArgumentList '/S' -Verb RunAs -Wait` + ], { silent: true, showCommand: true }); + await this.installHub(); + } else if (process.platform === 'linux') { await Exec('sudo', ['sh', '-c', `#!/bin/bash set -e wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | sudo tee /usr/share/keyrings/Unity_Technologies_ApS.gpg >/dev/null From 52de3c6eb2f7a5ebe9c8e97abae642a65830d344 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 20:22:11 -0400 Subject: [PATCH 24/26] guard legacy for monodevelop dir cleanup --- src/unity-editor.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 610d805b..54fa8455 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -336,11 +336,14 @@ export class UnityEditor { '-Command', `Start-Process -FilePath "${uninstallPath}" -ArgumentList "/S" -Wait` ], { silent: true, showCommand: true }); - // also delete the editor root directory if it still exists + // delete the editor root directory if it still exists await DeleteDirectory(editorDir); - // also delete the MonoDevelop directory one level up if it still exists - const monoDevelopDir = path.join(path.dirname(editorDir), 'MonoDevelop'); - await DeleteDirectory(monoDevelopDir); + + if (this.version.isLegacy()) { + // delete the MonoDevelop that is a sibling of the Unity editor directory + const monoDevelopDir = path.join(path.dirname(editorDir), 'MonoDevelop'); + await DeleteDirectory(monoDevelopDir); + } break; } } From 50a729a5efaa54c87dd6e20723be1841e9d2befe Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 21:16:21 -0400 Subject: [PATCH 25/26] rework versioning --- src/unity-hub.ts | 60 ++++++++++++++++++++----------------- tests/unity-hub.test.ts | 27 +++++++++++++++++ tests/unity-version.test.ts | 16 +++++----- 3 files changed, 67 insertions(+), 36 deletions(-) diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 200d29c6..11e8e094 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -24,9 +24,15 @@ import { import { UnityReleasesClient, GetUnityReleasesData, - UnityRelease + UnityRelease, + Release } from '@rage-against-the-pixel/unity-releases-api'; +interface ReleaseInfo { + unityRelease: UnityRelease; + unityVersion: UnityVersion; +} + export class UnityHub { /** The path to the Unity Hub executable. */ public readonly executable: string; @@ -540,8 +546,8 @@ chmod -R 777 "$hubPath"`]); resolvedVersion = resolvedVersion.findMatch(releases); } - if (!resolvedVersion.changeset) { - const unityReleaseInfo: UnityRelease = await this.getEditorReleaseInfo(resolvedVersion); + if (!resolvedVersion?.changeset) { + const unityReleaseInfo: UnityRelease = await this.GetEditorReleaseInfo(resolvedVersion); resolvedVersion = new UnityVersion(unityReleaseInfo.version, unityReleaseInfo.shortRevision, resolvedVersion.architecture); } } catch (error) { @@ -781,7 +787,13 @@ done } } - private async getEditorReleaseInfo(unityVersion: UnityVersion): Promise { + /** + * Gets the specified Unity release info from the Unity Releases API. + * Supports querying by exact version or by prefix (e.g., "2020", "2020.1", "2021.x", "2021.3.x"). + * @param unityVersion The Unity version to get the release info for. + * @returns The Unity release info. + */ + public async GetEditorReleaseInfo(unityVersion: UnityVersion): Promise { // Prefer querying the releases API with the exact fully-qualified Unity version (e.g., 2022.3.10f1). // If we don't have a fully-qualified version, use the most specific prefix available: // - "YYYY.M" when provided (e.g., 6000.1) @@ -792,6 +804,7 @@ done version = unityVersion.version; } else { const match = unityVersion.version.match(/^(\d{1,4})(?:\.(\d+))?/); + if (match) { version = match[2] ? `${match[1]}.${match[2]}` : match[1]!; } else { @@ -820,7 +833,8 @@ done version: version, architecture: [unityVersion.architecture], platform: getPlatform(), - limit: 1, + limit: 10, + order: 'RELEASE_DATE_DESC', } }; @@ -838,32 +852,22 @@ done this.logger.debug(`Found Unity Release: ${JSON.stringify(data, null, 2)}`); // Filter to stable 'f' releases only unless the user explicitly asked for a pre-release const isExplicitPrerelease = /[abcpx]$/.test(unityVersion.version) || /[abcpx]/.test(unityVersion.version); - const results = (data.results || []) - .filter(r => isExplicitPrerelease ? true : /f\d+$/.test(r.version)) - // Sort descending by minor, patch, f-number where possible; fallback to semver coercion - .sort((a, b) => { - const parse = (v: string) => { - const m = v.match(/(\d{1,4})\.(\d+)\.(\d+)([abcfpx])(\d+)/); - return m ? [parseInt(m[2]!), parseInt(m[3]!), m[4], parseInt(m[5]!)] as [number, number, string, number] : [0, 0, 'f', 0] as [number, number, string, number]; - }; - const [aMinor, aPatch, aTag, aNum] = parse(a.version); - const [bMinor, bPatch, bTag, bNum] = parse(b.version); - // Prefer higher minor - if (aMinor !== bMinor) return bMinor - aMinor; - // Then higher patch - if (aPatch !== bPatch) return bPatch - aPatch; - // Tag order: f > p > c > b > a > x - const order = { f: 5, p: 4, c: 3, b: 2, a: 1, x: 0 } as Record; - if (order[aTag] !== order[bTag]) return (order[bTag] || 0) - (order[aTag] || 0); - return bNum - aNum; - }); - - if (results.length === 0) { + const releases: ReleaseInfo[] = (data.results || []) + .filter(release => isExplicitPrerelease || release.version.includes('f')) + .map(release => ({ + unityRelease: release, + unityVersion: new UnityVersion(release.version, release.shortRevision, unityVersion.architecture) + })); + + if (releases.length === 0) { throw new Error(`No suitable Unity releases (stable) found for version: ${version}`); } - this.logger.debug(`Found Unity Release: ${JSON.stringify({ query: version, picked: results[0] }, null, 2)}`); - return results[0]!; + releases.sort((a, b) => UnityVersion.compare(b.unityVersion, a.unityVersion)); + + const latest = releases[0]!.unityRelease!; + this.logger.debug(`Latest Unity Release: ${latest.version} (${latest.shortRevision}) - ${latest.recommended}`); + return latest; } private async fallbackVersionLookup(unityVersion: UnityVersion): Promise { diff --git a/tests/unity-hub.test.ts b/tests/unity-hub.test.ts index 5f8ddc2a..b961a5e2 100644 --- a/tests/unity-hub.test.ts +++ b/tests/unity-hub.test.ts @@ -1,4 +1,7 @@ +import { UnityRelease } from '@rage-against-the-pixel/unity-releases-api'; import { UnityHub } from '../src/unity-hub'; +import { UnityVersion } from '../src/unity-version'; +import { Logger, LogLevel } from '../src/logging'; jest.setTimeout(30000); // UnityHub operations can be slow @@ -27,4 +30,28 @@ describe('UnityHub', () => { console.warn('No Unity editors installed. Skipping ListInstalledEditors tests.'); } }); + + it('should get latest editor release info for a partial version YYYY', async () => { + const unityHub = new UnityHub(); + const version = new UnityVersion('2021'); + const releaseInfo: UnityRelease = await unityHub.GetEditorReleaseInfo(version); + expect(releaseInfo).toBeDefined(); + expect(releaseInfo.version).toMatch(/^2021.3.\d+[abcfpx]\d+$/); + }); + + it('should get latest editor release info for a partial version YYYY.x', async () => { + const unityHub = new UnityHub(); + const version = new UnityVersion('2021.x'); + const releaseInfo: UnityRelease = await unityHub.GetEditorReleaseInfo(version); + expect(releaseInfo).toBeDefined(); + expect(releaseInfo.version).toMatch(/^2021.3.\d+[abcfpx]\d+$/); + }); + + it('should get latest editor release info for a partial version YYYY.Y.x', async () => { + const unityHub = new UnityHub(); + const version = new UnityVersion('2020.3.x'); + const releaseInfo: UnityRelease = await unityHub.GetEditorReleaseInfo(version); + expect(releaseInfo).toBeDefined(); + expect(releaseInfo.version).toMatch(/^2020.3.\d+[abcfpx]\d+$/); + }); }); \ No newline at end of file diff --git a/tests/unity-version.test.ts b/tests/unity-version.test.ts index 1429b9e0..9139c841 100644 --- a/tests/unity-version.test.ts +++ b/tests/unity-version.test.ts @@ -24,18 +24,18 @@ describe('UnityVersion', () => { it('finds latest final release when provided a partial version', () => { const available = [ - 'Unity 2021.3.1f1', - 'Unity 2021.3.6f1 (abcdef123456)', - 'Unity 2021.3.4p2', - 'Unity 2021.3.5f2' + '2021.3.5f2', + '2021.3.1f1', + '2021.3.4p2', + '2021.3.5f1', + '2021.3.2f1 (abcdef123456)', ]; - const version = new UnityVersion('2021.3'); + const version = new UnityVersion('2021.x'); const match = version.findMatch(available); - expect(match.version).toBe('2021.3.6f1'); - // ensure comparison recognizes 2021.3.6f1 as newer than 2021.3.5f2 - const older = new UnityVersion('2021.3.5f2'); + expect(match.version).toBe('2021.3.5f2'); + const older = new UnityVersion('2021.3.5f1'); expect(UnityVersion.compare(match, older)).toBeGreaterThan(0); }); }); From 2282ddf8912c6a38d11ec81fec1204fe1fe9fab3 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 6 Oct 2025 23:20:39 -0400 Subject: [PATCH 26/26] remove 2020.x --- .github/workflows/build-options.json | 1 - src/unity-editor.ts | 3 +++ src/unity-hub.ts | 6 ++++-- tests/unity-hub.test.ts | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index ad7bae7b..d5847286 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -6,7 +6,6 @@ ], "unity-version": [ "4.7.2", - "2020.x", "2021.x", "2022.x", "6000.0.x", diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 54fa8455..4ec1d1c6 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -319,6 +319,9 @@ export class UnityEditor { return editorRootPath; } + /** + * Uninstall the Unity Editor. + */ public async Uninstall(): Promise { switch (process.platform) { case 'darwin': diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 11e8e094..a486c412 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -849,7 +849,6 @@ done throw new Error(`No Unity releases found for version: ${version}`); } - this.logger.debug(`Found Unity Release: ${JSON.stringify(data, null, 2)}`); // Filter to stable 'f' releases only unless the user explicitly asked for a pre-release const isExplicitPrerelease = /[abcpx]$/.test(unityVersion.version) || /[abcpx]/.test(unityVersion.version); const releases: ReleaseInfo[] = (data.results || []) @@ -865,8 +864,11 @@ done releases.sort((a, b) => UnityVersion.compare(b.unityVersion, a.unityVersion)); + this.logger.debug(`Found ${releases.length} matching Unity releases for version: ${version}`); + releases.forEach(release => { + this.logger.debug(` - ${release.unityRelease.version} (${release.unityRelease.shortRevision}) - ${release.unityRelease.recommended}`); + }); const latest = releases[0]!.unityRelease!; - this.logger.debug(`Latest Unity Release: ${latest.version} (${latest.shortRevision}) - ${latest.recommended}`); return latest; } diff --git a/tests/unity-hub.test.ts b/tests/unity-hub.test.ts index b961a5e2..de40a86d 100644 --- a/tests/unity-hub.test.ts +++ b/tests/unity-hub.test.ts @@ -49,9 +49,9 @@ describe('UnityHub', () => { it('should get latest editor release info for a partial version YYYY.Y.x', async () => { const unityHub = new UnityHub(); - const version = new UnityVersion('2020.3.x'); + const version = new UnityVersion('2021.3.x'); const releaseInfo: UnityRelease = await unityHub.GetEditorReleaseInfo(version); expect(releaseInfo).toBeDefined(); - expect(releaseInfo.version).toMatch(/^2020.3.\d+[abcfpx]\d+$/); + expect(releaseInfo.version).toMatch(/^2021.3.\d+[abcfpx]\d+$/); }); }); \ No newline at end of file