Skip to content

Commit 910a11f

Browse files
unity-cli@v1.0.3
- move cli invoke into its own class and update bin pointer
1 parent 92c37bf commit 910a11f

4 files changed

Lines changed: 329 additions & 330 deletions

File tree

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rage-against-the-pixel/unity-cli",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "A command line utility for the Unity Game Engine.",
55
"author": "RageAgainstThePixel",
66
"license": "MIT",
@@ -34,7 +34,7 @@
3434
],
3535
"main": "dist/index.js",
3636
"bin": {
37-
"unity-cli": "dist/index.js"
37+
"unity-cli": "dist/cli.js"
3838
},
3939
"files": [
4040
"dist"

src/cli.ts

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
#!/usr/bin/env node
2+
3+
import 'source-map-support/register';
4+
import * as fs from 'fs';
5+
import * as os from 'os';
6+
import { Command } from 'commander';
7+
import path, { join } from 'path';
8+
import { LicenseType, LicensingClient } from './license-client';
9+
import { PromptForSecretInput } from './utilities';
10+
import { UnityHub } from './unity-hub';
11+
import { Logger, LogLevel } from './logging';
12+
import { UnityVersion } from './unity-version';
13+
import { UnityProject } from './unity-project';
14+
import { CheckAndroidSdkInstalled } from './android-sdk';
15+
import { UnityEditor } from './unity-editor';
16+
17+
const pkgPath = join(__dirname, '..', 'package.json');
18+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
19+
const program = new Command();
20+
21+
program.name('unity-cli')
22+
.description('A command line utility for the Unity Game Engine.')
23+
.version(pkg.version);
24+
25+
program.command('license-version')
26+
.description('Print the version of the Unity License Client.')
27+
.action(async () => {
28+
const client = new LicensingClient();
29+
await client.Version();
30+
});
31+
32+
program.command('activate-license')
33+
.description('Activate a Unity license.')
34+
.option('-e, --email <email>', 'Email associated with the Unity account. Required when activating a personal or professional license.')
35+
.option('-p, --password <password>', 'Password for the Unity account. Required when activating a personal or professional license.')
36+
.option('-s, --serial <serial>', 'License serial number. Required when activating a professional license.')
37+
.option('-l, --license <license>', 'License type (personal, professional, floating).')
38+
.option('-c, --config <config>', 'Path to the configuration file. Required when activating a floating license.')
39+
.option('--verbose', 'Enable verbose logging.')
40+
.action(async (options) => {
41+
if (options.verbose) {
42+
Logger.instance.logLevel = LogLevel.DEBUG;
43+
}
44+
45+
Logger.instance.debug(JSON.stringify(options));
46+
47+
const client = new LicensingClient();
48+
const licenseStr: string = options.license?.toString()?.trim();
49+
50+
if (!licenseStr || licenseStr.length === 0) {
51+
throw new Error('License type is required. Use -l or --license to specify it.');
52+
}
53+
54+
const licenseType: LicenseType = options.license.toLowerCase() as LicenseType;
55+
56+
if (![LicenseType.personal, LicenseType.professional, LicenseType.floating].includes(licenseType)) {
57+
throw new Error(`Invalid license type: ${licenseType}`);
58+
}
59+
60+
if (licenseType !== LicenseType.floating) {
61+
if (!options.email) {
62+
options.email = await PromptForSecretInput('Email: ');
63+
}
64+
65+
if (!options.password) {
66+
options.password = await PromptForSecretInput('Password: ');
67+
}
68+
69+
if (licenseType === LicenseType.professional && !options.serial) {
70+
options.serial = await PromptForSecretInput('Serial: ');
71+
}
72+
}
73+
74+
await client.Activate(licenseType, options.config, options.serial, options.email, options.password);
75+
});
76+
77+
program.command('return-license')
78+
.description('Return a Unity license.')
79+
.option('-l, --license <license>', 'License type (personal, professional, floating)')
80+
.option('--verbose', 'Enable verbose logging.')
81+
.action(async (options) => {
82+
if (options.verbose) {
83+
Logger.instance.logLevel = LogLevel.DEBUG;
84+
}
85+
86+
Logger.instance.debug(JSON.stringify(options));
87+
88+
const client = new LicensingClient();
89+
const licenseStr: string = options.license?.toString()?.trim();
90+
91+
if (!licenseStr || licenseStr.length === 0) {
92+
throw new Error('License type is required. Use -l or --license to specify it.');
93+
}
94+
95+
const licenseType: LicenseType = licenseStr.toLowerCase() as LicenseType;
96+
97+
if (![LicenseType.personal, LicenseType.professional, LicenseType.floating].includes(licenseType)) {
98+
throw new Error(`Invalid license type: ${licenseType}`);
99+
}
100+
101+
await client.Deactivate(licenseType);
102+
});
103+
104+
program.command('hub-version')
105+
.description('Print the version of the Unity Hub.')
106+
.action(async () => {
107+
const unityHub = new UnityHub();
108+
const version = await unityHub.Version();
109+
process.stdout.write(`${version}\n`);
110+
});
111+
112+
program.command('hub-install')
113+
.description('Install the Unity Hub.')
114+
.option('--verbose', 'Enable verbose logging.')
115+
.option('--json', 'Prints the last line of output as JSON string.')
116+
.action(async (options) => {
117+
if (options.verbose) {
118+
Logger.instance.logLevel = LogLevel.DEBUG;
119+
}
120+
121+
const unityHub = new UnityHub();
122+
const hubPath = await unityHub.Install();
123+
124+
if (options.json) {
125+
process.stdout.write(`\n${JSON.stringify({ UNITY_HUB_PATH: hubPath })}\n`);
126+
} else {
127+
process.stdout.write(`${hubPath}\n`);
128+
}
129+
});
130+
131+
program.command('hub-path')
132+
.description('Print the path to the Unity Hub executable.')
133+
.option('--json', 'Prints the last line of output as JSON string.')
134+
.action(async (options) => {
135+
const hub = new UnityHub();
136+
if (options.json) {
137+
process.stdout.write(`\n${JSON.stringify({ UNITY_HUB_PATH: hub.executable })}\n`);
138+
} else {
139+
process.stdout.write(`${hub.executable}\n`);
140+
}
141+
});
142+
143+
program.command('hub')
144+
.description('Run commands directly to the Unity Hub. (You need not to pass --headless or -- to this command).')
145+
.allowUnknownOption(true)
146+
.argument('<args...>', 'Arguments to pass to the Unity Hub executable.')
147+
.option('--verbose', 'Enable verbose logging.')
148+
.action(async (args: string[], options) => {
149+
if (options.verbose) {
150+
Logger.instance.logLevel = LogLevel.DEBUG;
151+
}
152+
153+
Logger.instance.debug(JSON.stringify({ args, options }));
154+
155+
const unityHub = new UnityHub();
156+
await unityHub.Exec(args, { silent: false, showCommand: Logger.instance.logLevel === LogLevel.DEBUG });
157+
});
158+
159+
program.command('setup-unity')
160+
.description('Sets up the environment for the specified project and finds or installs the Unity Editor version for it.')
161+
.option('-p, --unity-project <unityProjectPath>', 'The path to a Unity project or "none" to skip project detection.')
162+
.option('-u, --unity-version <unityVersion>', '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.')
163+
.option('-c, --changeset <changeset>', 'The Unity changeset to get (e.g. 1234567890ab).')
164+
.option('-a, --arch <architecture>', 'The Unity architecture to get (e.g. x86_64, arm64). Defaults to the architecture of the current process.')
165+
.option('-b, --build-targets <buildTargets>', 'The Unity build target to get (e.g. iOS, Android).')
166+
.option('-m, --modules <modules>', 'The Unity module to get (e.g. ios, android).')
167+
.option('-i, --install-path <installPath>', 'The path to install the Unity Editor to. By default, it will be installed to the default Unity Hub location.')
168+
.option('--verbose', 'Enable verbose logging.')
169+
.option('--json', 'Prints the last line of output as JSON string.')
170+
.action(async (options) => {
171+
if (options.verbose) {
172+
Logger.instance.logLevel = LogLevel.DEBUG;
173+
}
174+
175+
Logger.instance.debug(JSON.stringify(options));
176+
177+
let unityProject: UnityProject | undefined;
178+
179+
if (options.unityProject) {
180+
unityProject = await UnityProject.GetProject(options.unityProject);
181+
}
182+
183+
if (!options.unityVersion && !unityProject) {
184+
throw new Error('You must specify a Unity version or project with -u, --unity-version, -p, --unity-project.');
185+
}
186+
187+
const unityVersion = unityProject?.version ?? new UnityVersion(options.unityVersion, options.changeset);
188+
const modules: string[] = options.modules ? options.modules.split(/[ ,]+/).filter(Boolean) : [];
189+
const buildTargets: string[] = options.buildTargets ? options.buildTargets.split(/[ ,]+/).filter(Boolean) : [];
190+
const moduleBuildTargetMap = UnityHub.GetPlatformTargetModuleMap();
191+
192+
for (const target of buildTargets) {
193+
const module = moduleBuildTargetMap[target];
194+
195+
if (module === undefined) {
196+
if (target.toLowerCase() !== 'none') {
197+
Logger.instance.warn(`${target} is not a valid build target for ${os.type()}`);
198+
}
199+
200+
continue;
201+
}
202+
203+
if (!modules.includes(module)) {
204+
modules.push(module);
205+
}
206+
}
207+
208+
if (modules.includes('none') ||
209+
modules.includes('None')) {
210+
modules.length = 0;
211+
}
212+
213+
const unityHub = new UnityHub();
214+
const editorPath = await unityHub.GetEditor(unityVersion, modules);
215+
const output: { [key: string]: string } = {
216+
'UNITY_HUB_PATH': unityHub.executable,
217+
'UNITY_EDITOR': editorPath
218+
};
219+
220+
if (unityProject) {
221+
output['UNITY_PROJECT_PATH'] = unityProject.projectPath;
222+
223+
if (modules.includes('android')) {
224+
await CheckAndroidSdkInstalled(editorPath, unityProject.projectPath);
225+
}
226+
}
227+
228+
if (options.json) {
229+
process.stdout.write(`\n${JSON.stringify(output)}\n`);
230+
}
231+
});
232+
233+
program.command('create-project')
234+
.description('Create a new Unity project.')
235+
.option('--name <projectName>', 'The name of the new Unity project. If unspecified, the project will be created in the specified path or the current working directory.')
236+
.option('--path <projectPath>', 'The path to create the new Unity project. If unspecified, the current working directory will be used.')
237+
.option('--template <projectTemplate>', 'The name of the template package to use for creating the unity project. Supports regex patterns.', 'com.unity.template.3d(-cross-platform)?')
238+
.option('--unity-editor <unityEditorPath>', 'The path to the Unity Editor executable. If unspecified, the UNITY_EDITOR environment variable must be set.')
239+
.option('--verbose', 'Enable verbose logging.')
240+
.option('--json', 'Prints the last line of output as JSON string.')
241+
.action(async (options) => {
242+
if (options.verbose) {
243+
Logger.instance.logLevel = LogLevel.DEBUG;
244+
}
245+
246+
Logger.instance.debug(JSON.stringify(options));
247+
248+
const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR;
249+
250+
if (!editorPath || editorPath.length === 0) {
251+
throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR environment variable.');
252+
}
253+
254+
const unityEditor = new UnityEditor(editorPath);
255+
256+
if (!options.template || options.template.length === 0) {
257+
throw new Error('The project template name was not specified. Use -t or --template to specify it.');
258+
}
259+
260+
const templatePath = unityEditor.GetTemplatePath(options.template);
261+
const projectName = options.name?.toString()?.trim();
262+
263+
let projectPath = options.path?.toString()?.trim() || process.cwd();
264+
265+
if (projectName && projectName.length > 0) {
266+
projectPath = path.join(projectPath, projectName);
267+
}
268+
269+
await unityEditor.Run({
270+
projectPath: projectPath,
271+
args: [
272+
'-quit',
273+
'-nographics',
274+
'-batchmode',
275+
'-createProject', projectPath,
276+
'-cloneFromTemplate', templatePath
277+
]
278+
});
279+
280+
if (options.json) {
281+
process.stdout.write(`\n${JSON.stringify({ UNITY_PROJECT_PATH: projectPath })}\n`);
282+
}
283+
});
284+
285+
program.command('run')
286+
.description('Run command line args directly to the Unity Editor.')
287+
.option('--unity-editor <unityEditorPath>', 'The path to the Unity Editor executable. If unspecified, the UNITY_EDITOR environment variable must be set.')
288+
.option('--unity-project <unityProjectPath>', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.')
289+
.option('--log-name <logName>', 'The name of the log file.')
290+
.allowUnknownOption(true)
291+
.argument('<args...>', 'Arguments to pass to the Unity Editor executable.')
292+
.option('--verbose', 'Enable verbose logging.')
293+
.action(async (args: string[], options) => {
294+
if (options.verbose) {
295+
Logger.instance.logLevel = LogLevel.DEBUG;
296+
}
297+
298+
Logger.instance.debug(JSON.stringify({ options, args }));
299+
300+
const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR;
301+
302+
if (!editorPath || editorPath.length === 0) {
303+
throw new Error('The Unity Editor path was not specified. Use -e or --unity-editor to specify it, or set the UNITY_EDITOR environment variable.');
304+
}
305+
306+
const unityEditor = new UnityEditor(editorPath);
307+
const projectPath = options.unityProject?.toString()?.trim() || process.env.UNITY_PROJECT_PATH || process.cwd();
308+
const unityProject = await UnityProject.GetProject(projectPath);
309+
310+
if (!unityProject) {
311+
throw new Error(`The specified path is not a valid Unity project: ${projectPath}`);
312+
}
313+
314+
if (!args.includes('-logFile')) {
315+
const logPath = unityEditor.GenerateLogFilePath(unityProject.projectPath, options.logName);
316+
args.push('-logFile', logPath);
317+
}
318+
319+
await unityEditor.Run({
320+
args: [...args]
321+
});
322+
});
323+
324+
program.parse(process.argv);

0 commit comments

Comments
 (0)