|
3 | 3 | import 'source-map-support/register'; |
4 | 4 | import * as fs from 'fs'; |
5 | 5 | import * as os from 'os'; |
| 6 | +import * as tar from 'tar'; |
6 | 7 | import * as path from 'path'; |
7 | 8 | import { Command } from 'commander'; |
8 | 9 | import { UnityHub } from './unity-hub'; |
| 10 | +import { UnityEditor } from './unity-editor'; |
9 | 11 | import updateNotifier from "update-notifier"; |
10 | 12 | import { Logger, LogLevel } from './logging'; |
11 | | -import { UnityEditor } from './unity-editor'; |
12 | 13 | import { UnityVersion } from './unity-version'; |
13 | 14 | import { UnityProject } from './unity-project'; |
14 | 15 | import { ChildProcess, spawn } from 'child_process'; |
15 | | -import { PromptForSecretInput } from './utilities'; |
16 | 16 | import { CheckAndroidSdkInstalled } from './android-sdk'; |
17 | 17 | import { LicenseType, LicensingClient } from './license-client'; |
| 18 | +import { PromptForSecretInput, ResolveGlobToPath } from './utilities'; |
18 | 19 |
|
19 | 20 | const pkgPath = path.join(__dirname, '..', 'package.json'); |
20 | 21 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); |
@@ -340,45 +341,6 @@ program.command('uninstall-unity') |
340 | 341 |
|
341 | 342 | program.commandsGroup('Unity Editor:'); |
342 | 343 |
|
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 | | - |
382 | 344 | program.command('run') |
383 | 345 | .description('Run command line args directly to the Unity Editor.') |
384 | 346 | .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') |
555 | 517 | process.exit(0); |
556 | 518 | }); |
557 | 519 |
|
| 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 | + }); |
558 | 680 | program.parse(process.argv); |
0 commit comments