diff --git a/script/auth/connection.ts b/script/auth/connection.ts index 800772a..2aafc85 100644 --- a/script/auth/connection.ts +++ b/script/auth/connection.ts @@ -2,7 +2,7 @@ import * as chalk from "chalk"; import * as fs from "fs"; import * as path from "path"; import { Headers } from "../types"; -import { log } from "../ui/log"; +import { log } from "../ui/terminal"; import AccountManager = require("../management-sdk"); const packageJson = require("../../../package.json"); diff --git a/script/cli.ts b/script/cli.ts index 3de0272..2fc13b0 100644 --- a/script/cli.ts +++ b/script/cli.ts @@ -3,15 +3,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import * as parser from "./command-parser"; +import { createCommand } from "./parser/create-command"; +import { showHelp } from "./parser/commands"; import * as execute from "./command-executor"; import * as chalk from "chalk"; function run() { - const command = parser.createCommand(); + const command = createCommand(); if (!command) { - parser.showHelp(/*showRootDescription*/ false); + showHelp(/*showRootDescription*/ false); return; } diff --git a/script/command-executor.ts b/script/command-executor.ts index b9acca0..30fe4c6 100644 --- a/script/command-executor.ts +++ b/script/command-executor.ts @@ -54,9 +54,7 @@ import { ILoginConnectionInfo, serializeConnectionInfo, } from "./auth/connection"; -import { log } from "./ui/log"; -import { confirm, requestAccessKey } from "./ui/prompt"; -import { createUploadProgress } from "./ui/progress"; +import { confirm, createUploadProgress, log, requestAccessKey } from "./ui/terminal"; import { printAccessKeys, printAppList, diff --git a/script/command-parser.ts b/script/parser/commands.ts similarity index 71% rename from script/command-parser.ts rename to script/parser/commands.ts index 84dd65d..12594a3 100644 --- a/script/command-parser.ts +++ b/script/parser/commands.ts @@ -2,20 +2,22 @@ // Licensed under the MIT License. import * as yargs from "yargs"; -import * as cli from "../script/types/cli"; import * as chalk from "chalk"; -import backslash = require("backslash"); -import parseDuration = require("parse-duration"); -const packageJson = require("../../package.json"); +const packageJson = require("../../../package.json"); const ROLLOUT_PERCENTAGE_REGEX: RegExp = /^(100|[1-9][0-9]|[1-9])%?$/; const USAGE_PREFIX = "Usage: revopush"; // Command categories are: access-key, app, release, deployment, deployment-key, login, logout, register // Commands are the verb following the command category (e.g.: "add" in "app add"). let isValidCommand = false; + let wasHelpShown = false; +export function isHelpShown(): boolean { + return wasHelpShown; +} + export function showHelp(showRootDescription?: boolean): void { if (!wasHelpShown) { if (showRootDescription) { @@ -39,84 +41,6 @@ export function showHelp(showRootDescription?: boolean): void { } } -function accessKeyAdd(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs - .usage(USAGE_PREFIX + " access-key " + commandName + " ") - .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option arguments - .example( - "access-key " + commandName + ' "VSTS Integration"', - 'Creates a new access key with the name "VSTS Integration", which expires in 60 days' - ) - .example( - "access-key " + commandName + ' "One time key" --ttl 5m', - 'Creates a new access key with the name "One time key", which expires in 5 minutes' - ) - .option("ttl", { - default: "60d", - demand: false, - description: "Duration string which specifies the amount of time that the access key should remain valid for (e.g 5m, 60d, 1y)", - type: "string", - }); - - addCommonConfiguration(yargs); -} - -function accessKeyPatch(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs - .usage(USAGE_PREFIX + " access-key " + commandName + " ") - .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option arguments - .example( - "access-key " + commandName + ' "Key for build server" --name "Key for CI machine"', - 'Renames the access key named "Key for build server" to "Key for CI machine"' - ) - .example( - "access-key " + commandName + ' "Key for build server" --ttl 7d', - 'Updates the access key named "Key for build server" to expire in 7 days' - ) - .option("name", { - default: null, - demand: false, - description: "Display name for the access key", - type: "string", - }) - .option("ttl", { - default: null, - demand: false, - description: "Duration string which specifies the amount of time that the access key should remain valid for (e.g 5m, 60d, 1y)", - type: "string", - }); - addCommonConfiguration(yargs); -} - -function accessKeyList(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs - .usage(USAGE_PREFIX + " access-key " + commandName + " [options]") - .demand(/*count*/ 0, /*max*/ 0) - .example("access-key " + commandName, "Lists your access keys in tabular format") - .example("access-key " + commandName + " --format json", "Lists your access keys in JSON format") - .option("format", { - default: "table", - demand: false, - description: 'Output format to display your access keys with ("json" or "table")', - type: "string", - }); - - addCommonConfiguration(yargs); -} - -function accessKeyRemove(commandName: string, yargs: yargs.Argv): void { - isValidCommand = true; - yargs - .usage(USAGE_PREFIX + " access-key " + commandName + " ") - .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option arguments - .example("access-key " + commandName + ' "VSTS Integration"', 'Removes the "VSTS Integration" access key'); - - addCommonConfiguration(yargs); -} - // Options shared by release-react and release-expo function addReactReleaseOptions(yargs: yargs.Argv): void { yargs @@ -316,6 +240,97 @@ function addCommonConfiguration(yargs: yargs.Argv): void { .fail(() => showHelp()); // Suppress the default error message. } +function isValidRollout(args: any): boolean { + const rollout: string = args["rollout"]; + if (rollout && !ROLLOUT_PERCENTAGE_REGEX.test(rollout)) { + return false; + } + + return true; +} + +function checkValidReleaseOptions(args: any): boolean { + return isValidRollout(args) && !!args["deploymentName"]; +} + +function accessKeyAdd(commandName: string, yargs: yargs.Argv): void { + isValidCommand = true; + yargs + .usage(USAGE_PREFIX + " access-key " + commandName + " ") + .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option arguments + .example( + "access-key " + commandName + ' "VSTS Integration"', + 'Creates a new access key with the name "VSTS Integration", which expires in 60 days' + ) + .example( + "access-key " + commandName + ' "One time key" --ttl 5m', + 'Creates a new access key with the name "One time key", which expires in 5 minutes' + ) + .option("ttl", { + default: "60d", + demand: false, + description: "Duration string which specifies the amount of time that the access key should remain valid for (e.g 5m, 60d, 1y)", + type: "string", + }); + + addCommonConfiguration(yargs); +} + +function accessKeyPatch(commandName: string, yargs: yargs.Argv): void { + isValidCommand = true; + yargs + .usage(USAGE_PREFIX + " access-key " + commandName + " ") + .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option arguments + .example( + "access-key " + commandName + ' "Key for build server" --name "Key for CI machine"', + 'Renames the access key named "Key for build server" to "Key for CI machine"' + ) + .example( + "access-key " + commandName + ' "Key for build server" --ttl 7d', + 'Updates the access key named "Key for build server" to expire in 7 days' + ) + .option("name", { + default: null, + demand: false, + description: "Display name for the access key", + type: "string", + }) + .option("ttl", { + default: null, + demand: false, + description: "Duration string which specifies the amount of time that the access key should remain valid for (e.g 5m, 60d, 1y)", + type: "string", + }); + addCommonConfiguration(yargs); +} + +function accessKeyList(commandName: string, yargs: yargs.Argv): void { + isValidCommand = true; + yargs + .usage(USAGE_PREFIX + " access-key " + commandName + " [options]") + .demand(/*count*/ 0, /*max*/ 0) + .example("access-key " + commandName, "Lists your access keys in tabular format") + .example("access-key " + commandName + " --format json", "Lists your access keys in JSON format") + .option("format", { + default: "table", + demand: false, + description: 'Output format to display your access keys with ("json" or "table")', + type: "string", + }); + + addCommonConfiguration(yargs); +} + +function accessKeyRemove(commandName: string, yargs: yargs.Argv): void { + isValidCommand = true; + yargs + .usage(USAGE_PREFIX + " access-key " + commandName + " ") + .demand(/*count*/ 1, /*max*/ 1) // Require exactly one non-option arguments + .example("access-key " + commandName + ' "VSTS Integration"', 'Removes the "VSTS Integration" access key'); + + addCommonConfiguration(yargs); +} + function appList(commandName: string, yargs: yargs.Argv): void { isValidCommand = true; yargs @@ -992,481 +1007,3 @@ yargs .version(packageJson.version) .wrap(/*columnLimit*/ null) .fail(() => showHelp(/*showRootDescription*/ true)).argv; // Suppress the default error message. - -export function createCommand(): cli.ICommand { - let cmd: cli.ICommand; - - const argv = yargs.parseSync(); - - if (!wasHelpShown && argv._ && argv._.length > 0) { - // Create a command object - const arg0: any = argv._[0]; - const arg1: any = argv._[1]; - const arg2: any = argv._[2]; - const arg3: any = argv._[3]; - const arg4: any = argv._[4]; - - switch (arg0) { - case "access-key": - switch (arg1) { - case "add": - if (arg2) { - cmd = { type: cli.CommandType.accessKeyAdd }; - const accessKeyAddCmd = cmd as cli.IAccessKeyAddCommand; - accessKeyAddCmd.name = arg2; - const ttlOption: string = argv["ttl"] as any; - if (isDefined(ttlOption)) { - accessKeyAddCmd.ttl = parseDurationMilliseconds(ttlOption); - } - } - break; - - case "patch": - if (arg2) { - cmd = { type: cli.CommandType.accessKeyPatch }; - const accessKeyPatchCmd = cmd as cli.IAccessKeyPatchCommand; - accessKeyPatchCmd.oldName = arg2; - - const newNameOption: string = argv["name"] as any; - const ttlOption: string = argv["ttl"] as any; - if (isDefined(newNameOption)) { - accessKeyPatchCmd.newName = newNameOption; - } - - if (isDefined(ttlOption)) { - accessKeyPatchCmd.ttl = parseDurationMilliseconds(ttlOption); - } - } - break; - - case "list": - case "ls": - cmd = { type: cli.CommandType.accessKeyList }; - - (cmd as cli.IAccessKeyListCommand).format = argv["format"] as any; - break; - - case "remove": - case "rm": - if (arg2) { - cmd = { type: cli.CommandType.accessKeyRemove }; - - (cmd as cli.IAccessKeyRemoveCommand).accessKey = arg2; - } - break; - } - break; - - case "app": - switch (arg1) { - case "add": - if (arg2) { - cmd = { type: cli.CommandType.appAdd }; - - (cmd as cli.IAppAddCommand).appName = arg2; - } - break; - - case "list": - case "ls": - cmd = { type: cli.CommandType.appList }; - - (cmd as cli.IAppListCommand).format = argv["format"] as any; - break; - - case "remove": - case "rm": - if (arg2) { - cmd = { type: cli.CommandType.appRemove }; - - (cmd as cli.IAppRemoveCommand).appName = arg2; - } - break; - - case "rename": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.appRename }; - - const appRenameCommand = cmd as cli.IAppRenameCommand; - - appRenameCommand.currentAppName = arg2; - appRenameCommand.newAppName = arg3; - } - break; - - case "transfer": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.appTransfer }; - - const appTransferCommand = cmd as cli.IAppTransferCommand; - - appTransferCommand.appName = arg2; - appTransferCommand.email = arg3; - } - break; - } - break; - - case "collaborator": - switch (arg1) { - case "add": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.collaboratorAdd }; - - (cmd as cli.ICollaboratorAddCommand).appName = arg2; - (cmd as cli.ICollaboratorAddCommand).email = arg3; - } - break; - - case "list": - case "ls": - if (arg2) { - cmd = { type: cli.CommandType.collaboratorList }; - - (cmd as cli.ICollaboratorListCommand).appName = arg2; - (cmd as cli.ICollaboratorListCommand).format = argv["format"] as any; - } - break; - - case "remove": - case "rm": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.collaboratorRemove }; - - (cmd as cli.ICollaboratorRemoveCommand).appName = arg2; - (cmd as cli.ICollaboratorRemoveCommand).email = arg3; - } - break; - } - break; - - case "debug": - cmd = { - type: cli.CommandType.debug, - platform: arg1, - } as cli.IDebugCommand; - - break; - - case "deployment": - switch (arg1) { - case "add": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.deploymentAdd }; - - const deploymentAddCommand = cmd as cli.IDeploymentAddCommand; - - deploymentAddCommand.appName = arg2; - deploymentAddCommand.deploymentName = arg3; - if (argv["key"]) { - deploymentAddCommand.key = argv["key"] as any; - } - } - break; - - case "clear": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.deploymentHistoryClear }; - - const deploymentHistoryClearCommand = cmd as cli.IDeploymentHistoryClearCommand; - - deploymentHistoryClearCommand.appName = arg2; - deploymentHistoryClearCommand.deploymentName = arg3; - } - break; - - case "list": - case "ls": - if (arg2) { - cmd = { type: cli.CommandType.deploymentList }; - - const deploymentListCommand = cmd as cli.IDeploymentListCommand; - - deploymentListCommand.appName = arg2; - deploymentListCommand.format = argv["format"] as any; - deploymentListCommand.displayKeys = argv["displayKeys"] as any; - } - break; - - case "remove": - case "rm": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.deploymentRemove }; - - const deploymentRemoveCommand = cmd as cli.IDeploymentRemoveCommand; - - deploymentRemoveCommand.appName = arg2; - deploymentRemoveCommand.deploymentName = arg3; - deploymentRemoveCommand.isForce = argv["force"] as any; - } - break; - - case "rename": - if (arg2 && arg3 && arg4) { - cmd = { type: cli.CommandType.deploymentRename }; - - const deploymentRenameCommand = cmd as cli.IDeploymentRenameCommand; - - deploymentRenameCommand.appName = arg2; - deploymentRenameCommand.currentDeploymentName = arg3; - deploymentRenameCommand.newDeploymentName = arg4; - } - break; - - case "history": - case "h": - if (arg2 && arg3) { - cmd = { type: cli.CommandType.deploymentHistory }; - - const deploymentHistoryCommand = cmd as cli.IDeploymentHistoryCommand; - - deploymentHistoryCommand.appName = arg2; - deploymentHistoryCommand.deploymentName = arg3; - deploymentHistoryCommand.format = argv["format"] as any; - deploymentHistoryCommand.displayAuthor = argv["displayAuthor"] as any; - } - break; - } - break; - - /* case "link": - cmd = { - type: cli.CommandType.link, - serverUrl: getServerUrl(arg1), - }; - break;*/ - - case "login": - cmd = { type: cli.CommandType.login }; - - const loginCommand = cmd as cli.ILoginCommand; - - loginCommand.apiServerUrl = getServerUrl(arg1); - loginCommand.appServerUrl = getServerUrl(arg2); - loginCommand.accessKey = argv["accessKey"] as any; - break; - - case "logout": - cmd = { type: cli.CommandType.logout }; - break; - - case "patch": - if (arg1 && arg2) { - cmd = { type: cli.CommandType.patch }; - - const patchCommand = cmd as cli.IPatchCommand; - - patchCommand.appName = arg1; - patchCommand.deploymentName = arg2; - patchCommand.label = argv["label"] as any; - // Description must be set to null to indicate that it is not being patched. - patchCommand.description = argv["description"] ? backslash(argv["description"]) : null; - patchCommand.disabled = argv["disabled"] as any; - patchCommand.mandatory = argv["mandatory"] as any; - patchCommand.rollout = getRolloutValue(argv["rollout"] as any); - patchCommand.appStoreVersion = argv["targetBinaryVersion"] as any; - // undefined = not provided (skip); null = reset to wildcard (empty string ""); string = retarget to that build. - patchCommand.buildNumber = argv["buildNumber"] !== undefined ? (argv["buildNumber"] as string) || null : undefined; - } - break; - - case "promote": - if (arg1 && arg2 && arg3) { - cmd = { type: cli.CommandType.promote }; - - const deploymentPromoteCommand = cmd as cli.IPromoteCommand; - - deploymentPromoteCommand.appName = arg1; - deploymentPromoteCommand.sourceDeploymentName = arg2; - deploymentPromoteCommand.destDeploymentName = arg3; - deploymentPromoteCommand.description = argv["description"] ? backslash(argv["description"]) : ""; - deploymentPromoteCommand.label = argv["label"] as any; - deploymentPromoteCommand.disabled = argv["disabled"] as any; - deploymentPromoteCommand.mandatory = argv["mandatory"] as any; - deploymentPromoteCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any; - deploymentPromoteCommand.rollout = getRolloutValue(argv["rollout"] as any); - deploymentPromoteCommand.appStoreVersion = argv["targetBinaryVersion"] as any; - } - break; - - case "register": - cmd = { type: cli.CommandType.register }; - - const registerCommand = cmd as cli.IRegisterCommand; - - registerCommand.serverUrl = getServerUrl(arg1); - break; - - case "release": - if (arg1 && arg2 && arg3) { - cmd = { type: cli.CommandType.release }; - - const releaseCommand = cmd as cli.IReleaseCommand; - - releaseCommand.appName = arg1; - releaseCommand.package = arg2; - releaseCommand.appStoreVersion = arg3; - releaseCommand.buildNumber = argv["buildNumber"] as any; - releaseCommand.deploymentName = argv["deploymentName"] as any; - releaseCommand.description = argv["description"] ? backslash(argv["description"]) : ""; - releaseCommand.disabled = argv["disabled"] as any; - releaseCommand.mandatory = argv["mandatory"] as any; - releaseCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any; - releaseCommand.rollout = getRolloutValue(argv["rollout"] as any); - } - break; - - case "release-react": - if (arg1 && arg2) { - cmd = getReleaseReactCommand(cli.CommandType.releaseReact, arg1, arg2, argv); - } - break; - - case "release-expo": - if (arg1 && arg2) { - cmd = getReleaseReactCommand(cli.CommandType.releaseExpo, arg1, arg2, argv); - } - break; - - case "release-native": - if (arg1 && arg2 && arg3) { - cmd = { type: cli.CommandType.releaseNative }; - - const releaseBinaryCommand = cmd as cli.IReleaseNativeCommand; - - releaseBinaryCommand.appName = arg1; - releaseBinaryCommand.platform = arg2; - releaseBinaryCommand.targetBinary = arg3; - releaseBinaryCommand.deploymentName = argv["deploymentName"] as any; - releaseBinaryCommand.appStoreVersion = argv["targetBinaryVersion"] as any; - releaseBinaryCommand.buildNumber = argv["buildNumber"] as any; - releaseBinaryCommand.initial = true; - releaseBinaryCommand.disabled = true; - releaseBinaryCommand.mandatory = false; - // TODO add support for releaseBinaryCommand.bundleName - // TODO add support for releaseBinaryCommand.outputDir - } - break; - - case "rollback": - if (arg1 && arg2) { - cmd = { type: cli.CommandType.rollback }; - - const rollbackCommand = cmd as cli.IRollbackCommand; - - rollbackCommand.appName = arg1; - rollbackCommand.deploymentName = arg2; - rollbackCommand.targetRelease = argv["targetRelease"] as any; - } - break; - - case "session": - switch (arg1) { - case "list": - case "ls": - cmd = { type: cli.CommandType.sessionList }; - - (cmd as cli.ISessionListCommand).format = argv["format"] as any; - break; - - case "remove": - case "rm": - if (arg2) { - cmd = { type: cli.CommandType.sessionRemove }; - - (cmd as cli.ISessionRemoveCommand).machineName = arg2; - } - break; - } - break; - - case "whoami": - cmd = { type: cli.CommandType.whoami }; - break; - } - - return cmd; - } -} - -function getReleaseReactCommand( - type: cli.CommandType, - appName: string, - platform: string, - argv: yargs.Arguments -): cli.IReleaseReactCommand { - const releaseReactCommand = { type } as cli.IReleaseReactCommand; - - releaseReactCommand.appName = appName; - releaseReactCommand.platform = platform; - - releaseReactCommand.appStoreVersion = argv["targetBinaryVersion"] as any; - releaseReactCommand.buildNumber = argv["buildNumber"] as any; - releaseReactCommand.bundleName = argv["bundleName"] as any; - releaseReactCommand.deploymentName = argv["deploymentName"] as any; - releaseReactCommand.disabled = argv["disabled"] as any; - releaseReactCommand.description = argv["description"] ? backslash(argv["description"]) : ""; - releaseReactCommand.development = argv["development"] as any; - releaseReactCommand.entryFile = argv["entryFile"] as any; - releaseReactCommand.gradleFile = argv["gradleFile"] as any; - releaseReactCommand.mandatory = argv["mandatory"] as any; - releaseReactCommand.initial = argv["initial"] as any; - releaseReactCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any; - releaseReactCommand.plistFile = argv["plistFile"] as any; - releaseReactCommand.plistFilePrefix = argv["plistFilePrefix"] as any; - releaseReactCommand.rollout = getRolloutValue(argv["rollout"] as any); - releaseReactCommand.sourcemapOutput = argv["sourcemapOutput"] as any; - releaseReactCommand.outputDir = argv["outputDir"] as any; - releaseReactCommand.useHermes = argv["useHermes"] as any; - releaseReactCommand.extraHermesFlags = argv["extraHermesFlags"] as any; - releaseReactCommand.podFile = argv["podFile"] as any; - releaseReactCommand.privateKeyPath = argv["privateKeyPath"] as any; - releaseReactCommand.xcodeProjectFile = argv["xcodeProjectFile"] as any; - releaseReactCommand.xcodeTargetName = argv["xcodeTargetName"] as any; - releaseReactCommand.buildConfigurationName = argv["buildConfigurationName"] as any; - releaseReactCommand.extraBundlerOptions = argv["extraBundlerOption"] as any; - releaseReactCommand.force = argv["force"] as any; - - return releaseReactCommand; -} - -function isValidRollout(args: any): boolean { - const rollout: string = args["rollout"]; - if (rollout && !ROLLOUT_PERCENTAGE_REGEX.test(rollout)) { - return false; - } - - return true; -} - -function checkValidReleaseOptions(args: any): boolean { - return isValidRollout(args) && !!args["deploymentName"]; -} - -function getRolloutValue(input: string): number { - return input ? parseInt(input.replace("%", "")) : null; -} - -function getServerUrl(url: string): string { - if (!url) return null; - - // Trim whitespace and a trailing slash (/) character. - url = url.trim(); - if (url[url.length - 1] === "/") { - url = url.substring(0, url.length - 1); - } - - url = url.replace(/^(https?):\\/, "$1://"); // Replace 'http(s):\' with 'http(s)://' for Windows Git Bash - - return url; -} - -function isDefined(object: any): boolean { - return object !== undefined && object !== null; -} - -function parseDurationMilliseconds(durationString: string): number { - // parse-duration v2 is ESM: the parser is `.default`, and `?? 0` restores v1's - // handling of unparseable input (v2 returns null instead of 0). - return Math.floor(parseDuration.default(durationString) ?? 0); -} diff --git a/script/parser/create-command.ts b/script/parser/create-command.ts new file mode 100644 index 0000000..e57c32e --- /dev/null +++ b/script/parser/create-command.ts @@ -0,0 +1,474 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as yargs from "yargs"; +import * as cli from "../types/cli"; +import backslash = require("backslash"); +import parseDuration = require("parse-duration"); +// Importing this module defines the commands with yargs, which must happen before argv is parsed +import { isHelpShown } from "./commands"; + +export function createCommand(): cli.ICommand { + let cmd: cli.ICommand; + + const argv = yargs.parseSync(); + + if (!isHelpShown() && argv._ && argv._.length > 0) { + // Create a command object + const arg0: any = argv._[0]; + const arg1: any = argv._[1]; + const arg2: any = argv._[2]; + const arg3: any = argv._[3]; + const arg4: any = argv._[4]; + + switch (arg0) { + case "access-key": + switch (arg1) { + case "add": + if (arg2) { + cmd = { type: cli.CommandType.accessKeyAdd }; + const accessKeyAddCmd = cmd as cli.IAccessKeyAddCommand; + accessKeyAddCmd.name = arg2; + const ttlOption: string = argv["ttl"] as any; + if (isDefined(ttlOption)) { + accessKeyAddCmd.ttl = parseDurationMilliseconds(ttlOption); + } + } + break; + + case "patch": + if (arg2) { + cmd = { type: cli.CommandType.accessKeyPatch }; + const accessKeyPatchCmd = cmd as cli.IAccessKeyPatchCommand; + accessKeyPatchCmd.oldName = arg2; + + const newNameOption: string = argv["name"] as any; + const ttlOption: string = argv["ttl"] as any; + if (isDefined(newNameOption)) { + accessKeyPatchCmd.newName = newNameOption; + } + + if (isDefined(ttlOption)) { + accessKeyPatchCmd.ttl = parseDurationMilliseconds(ttlOption); + } + } + break; + + case "list": + case "ls": + cmd = { type: cli.CommandType.accessKeyList }; + + (cmd as cli.IAccessKeyListCommand).format = argv["format"] as any; + break; + + case "remove": + case "rm": + if (arg2) { + cmd = { type: cli.CommandType.accessKeyRemove }; + + (cmd as cli.IAccessKeyRemoveCommand).accessKey = arg2; + } + break; + } + break; + + case "app": + switch (arg1) { + case "add": + if (arg2) { + cmd = { type: cli.CommandType.appAdd }; + + (cmd as cli.IAppAddCommand).appName = arg2; + } + break; + + case "list": + case "ls": + cmd = { type: cli.CommandType.appList }; + + (cmd as cli.IAppListCommand).format = argv["format"] as any; + break; + + case "remove": + case "rm": + if (arg2) { + cmd = { type: cli.CommandType.appRemove }; + + (cmd as cli.IAppRemoveCommand).appName = arg2; + } + break; + + case "rename": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.appRename }; + + const appRenameCommand = cmd as cli.IAppRenameCommand; + + appRenameCommand.currentAppName = arg2; + appRenameCommand.newAppName = arg3; + } + break; + + case "transfer": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.appTransfer }; + + const appTransferCommand = cmd as cli.IAppTransferCommand; + + appTransferCommand.appName = arg2; + appTransferCommand.email = arg3; + } + break; + } + break; + + case "collaborator": + switch (arg1) { + case "add": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.collaboratorAdd }; + + (cmd as cli.ICollaboratorAddCommand).appName = arg2; + (cmd as cli.ICollaboratorAddCommand).email = arg3; + } + break; + + case "list": + case "ls": + if (arg2) { + cmd = { type: cli.CommandType.collaboratorList }; + + (cmd as cli.ICollaboratorListCommand).appName = arg2; + (cmd as cli.ICollaboratorListCommand).format = argv["format"] as any; + } + break; + + case "remove": + case "rm": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.collaboratorRemove }; + + (cmd as cli.ICollaboratorRemoveCommand).appName = arg2; + (cmd as cli.ICollaboratorRemoveCommand).email = arg3; + } + break; + } + break; + + case "debug": + cmd = { + type: cli.CommandType.debug, + platform: arg1, + } as cli.IDebugCommand; + + break; + + case "deployment": + switch (arg1) { + case "add": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.deploymentAdd }; + + const deploymentAddCommand = cmd as cli.IDeploymentAddCommand; + + deploymentAddCommand.appName = arg2; + deploymentAddCommand.deploymentName = arg3; + if (argv["key"]) { + deploymentAddCommand.key = argv["key"] as any; + } + } + break; + + case "clear": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.deploymentHistoryClear }; + + const deploymentHistoryClearCommand = cmd as cli.IDeploymentHistoryClearCommand; + + deploymentHistoryClearCommand.appName = arg2; + deploymentHistoryClearCommand.deploymentName = arg3; + } + break; + + case "list": + case "ls": + if (arg2) { + cmd = { type: cli.CommandType.deploymentList }; + + const deploymentListCommand = cmd as cli.IDeploymentListCommand; + + deploymentListCommand.appName = arg2; + deploymentListCommand.format = argv["format"] as any; + deploymentListCommand.displayKeys = argv["displayKeys"] as any; + } + break; + + case "remove": + case "rm": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.deploymentRemove }; + + const deploymentRemoveCommand = cmd as cli.IDeploymentRemoveCommand; + + deploymentRemoveCommand.appName = arg2; + deploymentRemoveCommand.deploymentName = arg3; + deploymentRemoveCommand.isForce = argv["force"] as any; + } + break; + + case "rename": + if (arg2 && arg3 && arg4) { + cmd = { type: cli.CommandType.deploymentRename }; + + const deploymentRenameCommand = cmd as cli.IDeploymentRenameCommand; + + deploymentRenameCommand.appName = arg2; + deploymentRenameCommand.currentDeploymentName = arg3; + deploymentRenameCommand.newDeploymentName = arg4; + } + break; + + case "history": + case "h": + if (arg2 && arg3) { + cmd = { type: cli.CommandType.deploymentHistory }; + + const deploymentHistoryCommand = cmd as cli.IDeploymentHistoryCommand; + + deploymentHistoryCommand.appName = arg2; + deploymentHistoryCommand.deploymentName = arg3; + deploymentHistoryCommand.format = argv["format"] as any; + deploymentHistoryCommand.displayAuthor = argv["displayAuthor"] as any; + } + break; + } + break; + + /* case "link": + cmd = { + type: cli.CommandType.link, + serverUrl: getServerUrl(arg1), + }; + break;*/ + + case "login": + cmd = { type: cli.CommandType.login }; + + const loginCommand = cmd as cli.ILoginCommand; + + loginCommand.apiServerUrl = getServerUrl(arg1); + loginCommand.appServerUrl = getServerUrl(arg2); + loginCommand.accessKey = argv["accessKey"] as any; + break; + + case "logout": + cmd = { type: cli.CommandType.logout }; + break; + + case "patch": + if (arg1 && arg2) { + cmd = { type: cli.CommandType.patch }; + + const patchCommand = cmd as cli.IPatchCommand; + + patchCommand.appName = arg1; + patchCommand.deploymentName = arg2; + patchCommand.label = argv["label"] as any; + // Description must be set to null to indicate that it is not being patched. + patchCommand.description = argv["description"] ? backslash(argv["description"]) : null; + patchCommand.disabled = argv["disabled"] as any; + patchCommand.mandatory = argv["mandatory"] as any; + patchCommand.rollout = getRolloutValue(argv["rollout"] as any); + patchCommand.appStoreVersion = argv["targetBinaryVersion"] as any; + // undefined = not provided (skip); null = reset to wildcard (empty string ""); string = retarget to that build. + patchCommand.buildNumber = argv["buildNumber"] !== undefined ? (argv["buildNumber"] as string) || null : undefined; + } + break; + + case "promote": + if (arg1 && arg2 && arg3) { + cmd = { type: cli.CommandType.promote }; + + const deploymentPromoteCommand = cmd as cli.IPromoteCommand; + + deploymentPromoteCommand.appName = arg1; + deploymentPromoteCommand.sourceDeploymentName = arg2; + deploymentPromoteCommand.destDeploymentName = arg3; + deploymentPromoteCommand.description = argv["description"] ? backslash(argv["description"]) : ""; + deploymentPromoteCommand.label = argv["label"] as any; + deploymentPromoteCommand.disabled = argv["disabled"] as any; + deploymentPromoteCommand.mandatory = argv["mandatory"] as any; + deploymentPromoteCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any; + deploymentPromoteCommand.rollout = getRolloutValue(argv["rollout"] as any); + deploymentPromoteCommand.appStoreVersion = argv["targetBinaryVersion"] as any; + } + break; + + case "register": + cmd = { type: cli.CommandType.register }; + + const registerCommand = cmd as cli.IRegisterCommand; + + registerCommand.serverUrl = getServerUrl(arg1); + break; + + case "release": + if (arg1 && arg2 && arg3) { + cmd = { type: cli.CommandType.release }; + + const releaseCommand = cmd as cli.IReleaseCommand; + + releaseCommand.appName = arg1; + releaseCommand.package = arg2; + releaseCommand.appStoreVersion = arg3; + releaseCommand.buildNumber = argv["buildNumber"] as any; + releaseCommand.deploymentName = argv["deploymentName"] as any; + releaseCommand.description = argv["description"] ? backslash(argv["description"]) : ""; + releaseCommand.disabled = argv["disabled"] as any; + releaseCommand.mandatory = argv["mandatory"] as any; + releaseCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any; + releaseCommand.rollout = getRolloutValue(argv["rollout"] as any); + } + break; + + case "release-react": + if (arg1 && arg2) { + cmd = getReleaseReactCommand(cli.CommandType.releaseReact, arg1, arg2, argv); + } + break; + + case "release-expo": + if (arg1 && arg2) { + cmd = getReleaseReactCommand(cli.CommandType.releaseExpo, arg1, arg2, argv); + } + break; + + case "release-native": + if (arg1 && arg2 && arg3) { + cmd = { type: cli.CommandType.releaseNative }; + + const releaseBinaryCommand = cmd as cli.IReleaseNativeCommand; + + releaseBinaryCommand.appName = arg1; + releaseBinaryCommand.platform = arg2; + releaseBinaryCommand.targetBinary = arg3; + releaseBinaryCommand.deploymentName = argv["deploymentName"] as any; + releaseBinaryCommand.appStoreVersion = argv["targetBinaryVersion"] as any; + releaseBinaryCommand.buildNumber = argv["buildNumber"] as any; + releaseBinaryCommand.initial = true; + releaseBinaryCommand.disabled = true; + releaseBinaryCommand.mandatory = false; + // TODO add support for releaseBinaryCommand.bundleName + // TODO add support for releaseBinaryCommand.outputDir + } + break; + + case "rollback": + if (arg1 && arg2) { + cmd = { type: cli.CommandType.rollback }; + + const rollbackCommand = cmd as cli.IRollbackCommand; + + rollbackCommand.appName = arg1; + rollbackCommand.deploymentName = arg2; + rollbackCommand.targetRelease = argv["targetRelease"] as any; + } + break; + + case "session": + switch (arg1) { + case "list": + case "ls": + cmd = { type: cli.CommandType.sessionList }; + + (cmd as cli.ISessionListCommand).format = argv["format"] as any; + break; + + case "remove": + case "rm": + if (arg2) { + cmd = { type: cli.CommandType.sessionRemove }; + + (cmd as cli.ISessionRemoveCommand).machineName = arg2; + } + break; + } + break; + + case "whoami": + cmd = { type: cli.CommandType.whoami }; + break; + } + + return cmd; + } +} + +function getReleaseReactCommand( + type: cli.CommandType, + appName: string, + platform: string, + argv: yargs.Arguments +): cli.IReleaseReactCommand { + const releaseReactCommand = { type } as cli.IReleaseReactCommand; + + releaseReactCommand.appName = appName; + releaseReactCommand.platform = platform; + + releaseReactCommand.appStoreVersion = argv["targetBinaryVersion"] as any; + releaseReactCommand.buildNumber = argv["buildNumber"] as any; + releaseReactCommand.bundleName = argv["bundleName"] as any; + releaseReactCommand.deploymentName = argv["deploymentName"] as any; + releaseReactCommand.disabled = argv["disabled"] as any; + releaseReactCommand.description = argv["description"] ? backslash(argv["description"]) : ""; + releaseReactCommand.development = argv["development"] as any; + releaseReactCommand.entryFile = argv["entryFile"] as any; + releaseReactCommand.gradleFile = argv["gradleFile"] as any; + releaseReactCommand.mandatory = argv["mandatory"] as any; + releaseReactCommand.initial = argv["initial"] as any; + releaseReactCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any; + releaseReactCommand.plistFile = argv["plistFile"] as any; + releaseReactCommand.plistFilePrefix = argv["plistFilePrefix"] as any; + releaseReactCommand.rollout = getRolloutValue(argv["rollout"] as any); + releaseReactCommand.sourcemapOutput = argv["sourcemapOutput"] as any; + releaseReactCommand.outputDir = argv["outputDir"] as any; + releaseReactCommand.useHermes = argv["useHermes"] as any; + releaseReactCommand.extraHermesFlags = argv["extraHermesFlags"] as any; + releaseReactCommand.podFile = argv["podFile"] as any; + releaseReactCommand.privateKeyPath = argv["privateKeyPath"] as any; + releaseReactCommand.xcodeProjectFile = argv["xcodeProjectFile"] as any; + releaseReactCommand.xcodeTargetName = argv["xcodeTargetName"] as any; + releaseReactCommand.buildConfigurationName = argv["buildConfigurationName"] as any; + releaseReactCommand.extraBundlerOptions = argv["extraBundlerOption"] as any; + releaseReactCommand.force = argv["force"] as any; + + return releaseReactCommand; +} + +function getRolloutValue(input: string): number { + return input ? parseInt(input.replace("%", "")) : null; +} + +function getServerUrl(url: string): string { + if (!url) return null; + + // Trim whitespace and a trailing slash (/) character. + url = url.trim(); + if (url[url.length - 1] === "/") { + url = url.substring(0, url.length - 1); + } + + url = url.replace(/^(https?):\\/, "$1://"); // Replace 'http(s):\' with 'http(s)://' for Windows Git Bash + + return url; +} + +function isDefined(object: any): boolean { + return object !== undefined && object !== null; +} + +function parseDurationMilliseconds(durationString: string): number { + // parse-duration v2 is ESM: the parser is `.default`, and `?? 0` restores v1's + // handling of unparseable input (v2 returns null instead of 0). + return Math.floor(parseDuration.default(durationString) ?? 0); +} diff --git a/script/platforms/android/binary.ts b/script/platforms/android/binary.ts index 2bf2464..c46e598 100644 --- a/script/platforms/android/binary.ts +++ b/script/platforms/android/binary.ts @@ -7,7 +7,7 @@ import * as chalk from "chalk"; import * as protobuf from "protobufjs"; import * as yauzl from "yauzl"; -import { log } from "../../ui/log"; +import { log } from "../../ui/terminal"; import { createZipArchive } from "../../utils/file-utils"; export async function extractMetadataFromAndroid(extractFolder, outputFolder) { diff --git a/script/platforms/ios/binary.ts b/script/platforms/ios/binary.ts index 6f1b23c..1a26da1 100644 --- a/script/platforms/ios/binary.ts +++ b/script/platforms/ios/binary.ts @@ -8,7 +8,7 @@ import * as chalk from "chalk"; import * as plist from "plist"; import * as yauzl from "yauzl"; -import { log } from "../../ui/log"; +import { log } from "../../ui/terminal"; import { createZipArchive } from "../../utils/file-utils"; export async function extractMetadataFromIOS(ipaPath: string, outputFolder: string) { diff --git a/script/platforms/react-native/bundler.ts b/script/platforms/react-native/bundler.ts index 2eacf1a..43acf52 100644 --- a/script/platforms/react-native/bundler.ts +++ b/script/platforms/react-native/bundler.ts @@ -3,8 +3,7 @@ import * as fs from "fs"; import * as path from "path"; import * as chalk from "chalk"; -import { log } from "../../ui/log"; -import { getReactNativePackagePath } from "./project"; +import { log } from "../../ui/terminal"; export const spawn = childProcess.spawn; @@ -175,3 +174,13 @@ function getExpoCliPath(): string { 'Unable to resolve "@expo/cli". Please make sure it is installed in your project (e.g. "npm install --save-dev @expo/cli").' ); } + +export function getReactNativePackagePath(): string { + const result = childProcess.spawnSync("node", ["--print", "require.resolve('react-native/package.json')"]); + return result.status === 0 ? path.dirname(result.stdout.toString().trim()) : path.join("node_modules", "react-native"); +} + +export function getReactNativeVersion(): string | undefined { + const packageJson = path.join(getReactNativePackagePath(), "package.json"); + return fs.existsSync(packageJson) ? JSON.parse(fs.readFileSync(packageJson, "utf8")).version : undefined; +} diff --git a/script/platforms/react-native/hermes.ts b/script/platforms/react-native/hermes.ts index e2c10dd..cc6fb29 100644 --- a/script/platforms/react-native/hermes.ts +++ b/script/platforms/react-native/hermes.ts @@ -7,12 +7,12 @@ import * as chalk from "chalk"; import { coerce, gte } from "semver"; import * as cli from "../../types/cli"; -import { log } from "../../ui/log"; +import { log } from "../../ui/terminal"; import { downloadBlob, extractArchive, fileExists } from "../../utils/file-utils"; import type AccountManager = require("../../management-sdk"); import { getGradleHermesCommand, getGradleHermesEnabled, readGradleProperty } from "../android/gradle"; import { getXcodeDotEnvValue } from "../ios/xcode"; -import { getReactNativePackagePath, getReactNativeVersion } from "./project"; +import { getReactNativePackagePath, getReactNativeVersion } from "./bundler"; import { restoreSourceMapMetadata, SourceMapPaths } from "./source-maps"; const OS_BIN = ({ win32: "win64-bin", darwin: "osx-bin" } as Record)[process.platform] ?? "linux64-bin"; diff --git a/script/platforms/react-native/project.ts b/script/platforms/react-native/project.ts deleted file mode 100644 index 311477e..0000000 --- a/script/platforms/react-native/project.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as childProcess from "child_process"; - -export function getReactNativePackagePath(): string { - const result = childProcess.spawnSync("node", ["--print", "require.resolve('react-native/package.json')"]); - return result.status === 0 ? path.dirname(result.stdout.toString().trim()) : path.join("node_modules", "react-native"); -} - -export function getReactNativeVersion(): string | undefined { - const packageJson = path.join(getReactNativePackagePath(), "package.json"); - return fs.existsSync(packageJson) ? JSON.parse(fs.readFileSync(packageJson, "utf8")).version : undefined; -} diff --git a/script/ui/log.ts b/script/ui/log.ts deleted file mode 100644 index b53ba29..0000000 --- a/script/ui/log.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function log(message: unknown): void { - console.log(message); -} diff --git a/script/ui/printers.ts b/script/ui/printers.ts index ac5ea7d..2996b5c 100644 --- a/script/ui/printers.ts +++ b/script/ui/printers.ts @@ -1,7 +1,7 @@ import * as chalk from "chalk"; import * as cli from "../types/cli"; import { AccessKey, App, CollaboratorMap, CollaboratorProperties, Deployment, Package, PackageWithMetrics, Session } from "../types"; -import { log } from "./log"; +import { log } from "./terminal"; import AccountManager = require("../management-sdk"); import wordwrap = require("wordwrap"); diff --git a/script/ui/progress.ts b/script/ui/progress.ts deleted file mode 100644 index 98b15be..0000000 --- a/script/ui/progress.ts +++ /dev/null @@ -1,17 +0,0 @@ -const ProgressBar = require("progress"); - -/** Returns a callback that draws upload progress, given the percentage uploaded so far */ -export function createUploadProgress(): (currentProgress: number) => void { - let lastTotalProgress = 0; - const progressBar = new ProgressBar("Upload progress:[:bar] :percent :etas", { - complete: "=", - incomplete: " ", - width: 50, - total: 100, - }); - - return (currentProgress: number): void => { - progressBar.tick(currentProgress - lastTotalProgress); - lastTotalProgress = currentProgress; - }; -} diff --git a/script/ui/prompt.ts b/script/ui/terminal.ts similarity index 69% rename from script/ui/prompt.ts rename to script/ui/terminal.ts index 0622080..c7721af 100644 --- a/script/ui/prompt.ts +++ b/script/ui/terminal.ts @@ -1,7 +1,12 @@ import * as chalk from "chalk"; +const ProgressBar = require("progress"); const prompt = require("prompt"); +export function log(message: unknown): void { + console.log(message); +} + export const confirm = (message: string = "Are you sure?"): Promise => { message += " (y/N):"; return new Promise((resolve): void => { @@ -60,3 +65,19 @@ export function requestAccessKey(): Promise { ); }); } + +/** Returns a callback that draws upload progress, given the percentage uploaded so far */ +export function createUploadProgress(): (currentProgress: number) => void { + let lastTotalProgress = 0; + const progressBar = new ProgressBar("Upload progress:[:bar] :percent :etas", { + complete: "=", + incomplete: " ", + width: 50, + total: 100, + }); + + return (currentProgress: number): void => { + progressBar.tick(currentProgress - lastTotalProgress); + lastTotalProgress = currentProgress; + }; +} diff --git a/test/cli.ts b/test/cli.ts index 127a4ce..0817ea9 100644 --- a/test/cli.ts +++ b/test/cli.ts @@ -9,8 +9,7 @@ import * as sinon from "sinon"; import * as cmdexec from "../script/command-executor"; import * as bundler from "../script/platforms/react-native/bundler"; import * as cli from "../script/types/cli"; -import * as uiLog from "../script/ui/log"; -import * as uiPrompt from "../script/ui/prompt"; +import * as terminal from "../script/ui/terminal"; import { argValue, fakeBundlerProcess } from "./helpers/bundler"; import { restoreEnvAfterEach } from "./helpers/env"; import { DEFAULT_ACCESS_KEY_MAX_AGE, NOW, SdkStub, TEST_MACHINE_NAME } from "./helpers/sdk-stub"; @@ -119,9 +118,9 @@ describe("CLI", () => { beforeEach(() => { sinon.stub(cmdexec, "sdk").value(new SdkStub()); - confirm = sinon.stub(uiPrompt, "confirm").resolves(true); + confirm = sinon.stub(terminal, "confirm").resolves(true); sinon.stub(cmdexec, "createEmptyTempReleaseFolder").resolves(); - log = sinon.stub(uiLog, "log"); + log = sinon.stub(terminal, "log"); spawn = sinon.stub(bundler, "spawn").callsFake(fakeBundler); }); diff --git a/test/hermes.ts b/test/hermes.ts index 67128cf..eeaf556 100644 --- a/test/hermes.ts +++ b/test/hermes.ts @@ -2,7 +2,7 @@ import { strict as assert } from "assert"; import * as fs from "fs"; import * as path from "path"; import * as sinon from "sinon"; -import * as uiLog from "../script/ui/log"; +import * as terminal from "../script/ui/terminal"; import { findHermesc, getHermescBytecodeVersion, @@ -113,7 +113,7 @@ describe("hermes", () => { "android/app/build.gradle", 'react {\n hermesCommand = new File(["node", "--print", "require.resolve(\'hermes-compiler/package.json\', { paths: [require.resolve(\'react-native/package.json\')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/hermesc/%OS-BIN%/hermesc"\n}\n' ); - const log = sinon.stub(uiLog, "log"); + const log = sinon.stub(terminal, "log"); assert.equal(await resolvedHermesc({ platform: "android" }), compiler); sinon.assert.notCalled(log); @@ -285,7 +285,7 @@ describe("hermes", () => { let log: sinon.SinonStub; beforeEach(() => { - log = sinon.stub(uiLog, "log"); + log = sinon.stub(terminal, "log"); }); it("accepts the base bytecode when versions match", () => { diff --git a/test/release-size-flow.ts b/test/release-size-flow.ts index 4e8bf3c..2893df6 100644 --- a/test/release-size-flow.ts +++ b/test/release-size-flow.ts @@ -7,7 +7,7 @@ import * as sinon from "sinon"; import * as cmdexec from "../script/command-executor"; import * as bundler from "../script/platforms/react-native/bundler"; import * as cli from "../script/types/cli"; -import * as uiLog from "../script/ui/log"; +import * as terminal from "../script/ui/terminal"; import { ASSET_OPTIMIZATION_DOCS_URL, MAX_ASSET_SIZE_BYTES, @@ -46,7 +46,7 @@ describe("command-executor release size checks", () => { isAuthenticated: sinon.stub().resolves(true), release: sdkRelease, }); - sinon.stub(uiLog, "log"); + sinon.stub(terminal, "log"); sinon.stub(console, "log"); warn = sinon.stub(console, "warn"); });