Skip to content

Commit ebbcd4a

Browse files
unity-cli@v1.2.4
- add sign-package command to sign upm packages
1 parent 0ce2f47 commit ebbcd4a

3 files changed

Lines changed: 226 additions & 44 deletions

File tree

package-lock.json

Lines changed: 61 additions & 2 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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rage-against-the-pixel/unity-cli",
3-
"version": "1.2.3",
3+
"version": "1.2.4",
44
"description": "A command line utility for the Unity Game Engine.",
55
"author": "RageAgainstThePixel",
66
"license": "MIT",
@@ -54,6 +54,7 @@
5454
"glob": "11.0.3",
5555
"semver": "^7.7.2",
5656
"source-map-support": "^0.5.21",
57+
"tar": "^7.5.1",
5758
"update-notifier": "^7.3.1",
5859
"yaml": "^2.8.1"
5960
},

src/cli.ts

Lines changed: 163 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33
import 'source-map-support/register';
44
import * as fs from 'fs';
55
import * as os from 'os';
6+
import * as tar from 'tar';
67
import * as path from 'path';
78
import { Command } from 'commander';
89
import { UnityHub } from './unity-hub';
10+
import { UnityEditor } from './unity-editor';
911
import updateNotifier from "update-notifier";
1012
import { Logger, LogLevel } from './logging';
11-
import { UnityEditor } from './unity-editor';
1213
import { UnityVersion } from './unity-version';
1314
import { UnityProject } from './unity-project';
1415
import { ChildProcess, spawn } from 'child_process';
15-
import { PromptForSecretInput } from './utilities';
1616
import { CheckAndroidSdkInstalled } from './android-sdk';
1717
import { LicenseType, LicensingClient } from './license-client';
18+
import { PromptForSecretInput, ResolveGlobToPath } from './utilities';
1819

1920
const pkgPath = path.join(__dirname, '..', 'package.json');
2021
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
@@ -340,45 +341,6 @@ program.command('uninstall-unity')
340341

341342
program.commandsGroup('Unity Editor:');
342343

343-
program.command('open-project')
344-
.description('Open a Unity project in the Unity Editor.')
345-
.option('-p, --unity-project <unityProject>', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.')
346-
.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.')
347-
.option('--verbose', 'Enable verbose logging.')
348-
.action(async (options) => {
349-
if (options.verbose) {
350-
Logger.instance.logLevel = LogLevel.DEBUG;
351-
}
352-
353-
Logger.instance.debug(JSON.stringify(options));
354-
const projectPath = options.unityProject?.toString()?.trim() || process.env.UNITY_PROJECT_PATH || undefined;
355-
const unityProject = await UnityProject.GetProject(projectPath);
356-
357-
if (!unityProject) {
358-
Logger.instance.error(`The specified path is not a valid Unity project: ${projectPath}`);
359-
process.exit(1);
360-
}
361-
362-
let unityVersion: UnityVersion | undefined = unityProject?.version;
363-
364-
if (options.unityVersion) {
365-
unityVersion = new UnityVersion(options.unityVersion);
366-
}
367-
368-
const unityHub = new UnityHub();
369-
const unityEditor = await unityHub.GetEditor(unityVersion);
370-
371-
Logger.instance.info(`Opening project at "${unityProject.projectPath}" with Unity ${unityEditor.version}...`);
372-
373-
let child: ChildProcess | null = null;
374-
try {
375-
child = spawn(unityEditor.editorPath, ['-projectPath', unityProject.projectPath], { detached: true });
376-
child.unref();
377-
} finally {
378-
process.exit(child?.pid !== undefined ? 0 : 1);
379-
}
380-
});
381-
382344
program.command('run')
383345
.description('Run command line args directly to the Unity Editor.')
384346
.option('--unity-editor <unityEditor>', 'The path to the Unity Editor executable. If unspecified, --unity-project or the UNITY_EDITOR_PATH environment variable must be set.')
@@ -555,4 +517,164 @@ program.command('create-project')
555517
process.exit(0);
556518
});
557519

520+
program.command('open-project')
521+
.description('Open a Unity project in the Unity Editor.')
522+
.option('-p, --unity-project <unityProject>', 'The path to a Unity project. If unspecified, the UNITY_PROJECT_PATH environment variable or the current working directory will be used.')
523+
.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.')
524+
.option('--verbose', 'Enable verbose logging.')
525+
.action(async (options) => {
526+
if (options.verbose) {
527+
Logger.instance.logLevel = LogLevel.DEBUG;
528+
}
529+
530+
Logger.instance.debug(JSON.stringify(options));
531+
const projectPath = options.unityProject?.toString()?.trim() || process.env.UNITY_PROJECT_PATH || undefined;
532+
const unityProject = await UnityProject.GetProject(projectPath);
533+
534+
if (!unityProject) {
535+
Logger.instance.error(`The specified path is not a valid Unity project: ${projectPath}`);
536+
process.exit(1);
537+
}
538+
539+
let unityVersion: UnityVersion | undefined = unityProject?.version;
540+
541+
if (options.unityVersion) {
542+
unityVersion = new UnityVersion(options.unityVersion);
543+
}
544+
545+
const unityHub = new UnityHub();
546+
const unityEditor = await unityHub.GetEditor(unityVersion);
547+
548+
Logger.instance.info(`Opening project at "${unityProject.projectPath}" with Unity ${unityEditor.version}...`);
549+
550+
let child: ChildProcess | null = null;
551+
try {
552+
child = spawn(unityEditor.editorPath, ['-projectPath', unityProject.projectPath], { detached: true });
553+
child.unref();
554+
} finally {
555+
process.exit(child?.pid !== undefined ? 0 : 1);
556+
}
557+
});
558+
559+
program.commandsGroup("Unity Package Manager:")
560+
561+
program.command('sign-package')
562+
.description('Sign a Unity package.')
563+
.option('--package <package>', 'Required. The fully qualified path to the folder that contains the package.json file for the package you want to sign. Note: Don’t include package.json in this parameter value.')
564+
.option('--output <output>', 'Optional. The output directory where you want to save the signed tarball file (.tgz). If unspecified, the package contents will be updated in place with the signed .attestation.p7m file.')
565+
.option('--email <email>', 'Email associated with the Unity account. If unspecified, the UNITY_USERNAME environment variable will be used.')
566+
.option('--password <password>', 'The password of the Unity account. If unspecified, the UNITY_PASSWORD environment variable will be used.')
567+
.option('--organization <organization>', 'The Organization ID you copied from the Unity Cloud Dashboard. If unspecified, the UNITY_ORGANIZATION_ID environment variable will be used.')
568+
.option('--verbose', 'Enable verbose logging.')
569+
.action(async (options) => {
570+
if (options.verbose) {
571+
Logger.instance.logLevel = LogLevel.DEBUG;
572+
}
573+
574+
Logger.instance.debug(JSON.stringify(options));
575+
576+
const packagePath = path.normalize(options.package?.toString()?.trim());
577+
578+
if (!packagePath || packagePath.length === 0) {
579+
Logger.instance.error('The package path is required. Use --package to specify it.');
580+
process.exit(1);
581+
}
582+
583+
const packageJsonPath = path.join(packagePath, 'package.json');
584+
try {
585+
await fs.promises.access(packageJsonPath, fs.constants.R_OK);
586+
} catch {
587+
Logger.instance.error(`Failed to find a valid package.json file at: ${packageJsonPath}`);
588+
process.exit(1);
589+
}
590+
591+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
592+
593+
let outputPath = options.output?.toString()?.trim();
594+
595+
if (outputPath && outputPath.length > 0) {
596+
outputPath = path.resolve(outputPath);
597+
598+
if (outputPath.endsWith('.tgz')) {
599+
// remove .tgz if present
600+
outputPath = outputPath.substring(0, outputPath.length - 4);
601+
}
602+
} else {
603+
outputPath = path.join(path.resolve(packagePath, '..'));
604+
}
605+
606+
let username = options.email?.toString()?.trim() || process.env.UNITY_USERNAME || undefined;
607+
608+
if (!username || username.length === 0) {
609+
username = await PromptForSecretInput('Email: ');
610+
}
611+
612+
if (!username || username.length === 0) {
613+
Logger.instance.error('The email is required. Use --email to specify it.');
614+
process.exit(1);
615+
}
616+
617+
let password = options.password?.toString()?.trim() || process.env.UNITY_PASSWORD || undefined;
618+
619+
if (!password || password.length === 0) {
620+
password = await PromptForSecretInput('Password: ');
621+
}
622+
623+
if (!password || password.length === 0) {
624+
Logger.instance.error('The password is required. Use --password to specify it.');
625+
process.exit(1);
626+
}
627+
628+
let organization = options.organization?.toString()?.trim() || process.env.UNITY_ORGANIZATION_ID || undefined;
629+
630+
if (!organization || organization.length === 0) {
631+
organization = await PromptForSecretInput('Organization ID: ');
632+
}
633+
634+
if (!organization || organization.length === 0) {
635+
Logger.instance.error('The organization ID is required. Use --organization to specify it.');
636+
process.exit(1);
637+
}
638+
639+
// must use a unity editor 6000.3 or newer
640+
const unityVersion = new UnityVersion('6000.3.0b4');
641+
const unityHub = new UnityHub();
642+
const unityEditor = await unityHub.GetEditor(unityVersion);
643+
try {
644+
await unityEditor.Run({
645+
args: [
646+
'-batchmode',
647+
'-username', username,
648+
'-password', password,
649+
'-upmPack', packagePath, path.normalize(outputPath),
650+
'-cloudOrganization', organization
651+
]
652+
});
653+
} catch (error) {
654+
// currently the editor returns exit code 1 even on success
655+
} finally {
656+
if (fs.existsSync(outputPath)) {
657+
const pkg = await ResolveGlobToPath([outputPath, `${path.basename(packageJson.name)}*.tgz`]);
658+
Logger.instance.info(`Package signed successfully: ${pkg}`);
659+
660+
// if the output directory was not specified in the command options,
661+
// then unpack the .tgz file and overwrite the package contents.
662+
if (!options.output || options.output.length === 0) {
663+
await tar.x({
664+
file: pkg,
665+
cwd: packagePath,
666+
strip: 1
667+
});
668+
669+
Logger.instance.info(`Package contents extracted to: ${packagePath}`);
670+
fs.unlinkSync(pkg);
671+
}
672+
673+
process.exit(0);
674+
} else {
675+
Logger.instance.error('Failed to sign the package.');
676+
process.exit(1);
677+
}
678+
}
679+
});
558680
program.parse(process.argv);

0 commit comments

Comments
 (0)