From a140f14175aedfa958e1e9e71a888ab4855d7a72 Mon Sep 17 00:00:00 2001 From: Revopush Date: Thu, 15 Oct 2026 17:01:05 +0300 Subject: [PATCH] Move terminal helpers into ui/ and break imports of command-executor - log, confirm, requestAccessKey and the upload progress bar move to script/ui; the two upload progress copies become createUploadProgress() - Helper modules no longer import command-executor; takeHermesBaseBytecode receives the SDK - types.ts becomes types/index.ts - Remove the unused index.ts and the package "main", which pointed to a file that is not published Co-Authored-By: Claude Opus 5.5 --- package.json | 1 - script/binary-utils.ts | 2 +- script/command-executor.ts | 96 +++-------------------------- script/index.ts | 5 -- script/react-native-utils.ts | 4 +- script/{types.ts => types/index.ts} | 2 +- script/ui/log.ts | 3 + script/ui/progress.ts | 17 +++++ script/ui/prompt.ts | 62 +++++++++++++++++++ script/utils/hermes-utils.ts | 2 +- test/cli.ts | 6 +- test/hermes-utils.ts | 6 +- test/release-size-flow.ts | 3 +- 13 files changed, 104 insertions(+), 105 deletions(-) delete mode 100644 script/index.ts rename script/{types.ts => types/index.ts} (93%) create mode 100644 script/ui/log.ts create mode 100644 script/ui/progress.ts create mode 100644 script/ui/prompt.ts diff --git a/package.json b/package.json index 7495fe8..a27def5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "@revopush/code-push-cli", "version": "0.0.15", "description": "Management CLI for the CodePush service", - "main": "./script/cli.js", "scripts": { "start": "node ./bin/script/cli.js", "build": "tsc", diff --git a/script/binary-utils.ts b/script/binary-utils.ts index 45422ce..addd82f 100644 --- a/script/binary-utils.ts +++ b/script/binary-utils.ts @@ -2,7 +2,7 @@ import * as path from "path"; import * as fs from "fs"; import * as crypto from "crypto"; import * as chalk from "chalk"; -import { log } from "./command-executor"; +import { log } from "./ui/log"; import * as os from "os"; import * as yazl from "yazl"; import * as yauzl from "yauzl"; diff --git a/script/command-executor.ts b/script/command-executor.ts index e789296..6c11ebc 100644 --- a/script/command-executor.ts +++ b/script/command-executor.ts @@ -27,7 +27,7 @@ import { PackageInfo, Session, UpdateMetrics, -} from "../script/types"; +} from "./types"; import { getMinifyParams, getReactNativePackagePath, @@ -53,12 +53,13 @@ import AccountManager = require("./management-sdk"); import wordwrap = require("wordwrap"); import { ReactNativePackageInfo } from "./types/rest-definitions"; import { getExpoCliPath } from "./expo-utils"; +import { log } from "./ui/log"; +import { confirm, requestAccessKey } from "./ui/prompt"; +import { createUploadProgress } from "./ui/progress"; const opener = require("opener"); const plist = require("plist"); -const progress = require("progress"); -const prompt = require("prompt"); const Table = require("cli-table"); @@ -91,46 +92,12 @@ export interface PackageWithMetrics { metrics?: UpdateMetricsWithTotalActive; } -export const log = (message: string | any): void => console.log(message); export let sdk: AccountManager; export const spawn = childProcess.spawn; export const execSync = childProcess.execSync; let connectionInfo: ILoginConnectionInfo; -export const confirm = (message: string = "Are you sure?"): Promise => { - message += " (y/N):"; - return new Promise((resolve): void => { - prompt.message = ""; - prompt.delimiter = ""; - - prompt.start(); - - prompt.get( - { - properties: { - response: { - description: chalk.cyan(message), - }, - }, - }, - (err: any, result: any): void => { - const accepted = result.response && result.response.toLowerCase() === "y"; - const rejected = !result.response || result.response.toLowerCase() === "n"; - - if (accepted) { - resolve(true); - } else { - if (!rejected) { - console.log('Invalid response: "' + result.response + '"'); - } - resolve(false); - } - } - ); - }); -}; - async function accessKeyAdd(command: cli.IAccessKeyAddCommand): Promise { const accessKey: AccessKey = await sdk.addAccessKey(command.name, command.ttl); log(`Successfully created the "${command.name}" access key: ${accessKey.key}`); @@ -1274,7 +1241,7 @@ async function compileHermesBundle( baseReleaseTmpFolder: string ): Promise { await createEmptyTempReleaseFolder(baseReleaseTmpFolder); - const baseBytecode = await takeHermesBaseBytecode(command, baseReleaseTmpFolder, outputFolder, bundleName); + const baseBytecode = await takeHermesBaseBytecode(sdk, command, baseReleaseTmpFolder, outputFolder, bundleName); log(chalk.cyan("\nRunning hermes compiler...\n")); const bundle = path.join(outputFolder, bundleName); @@ -1581,18 +1548,7 @@ const doRelease = async ( isSingleFilePackage = false; } - let lastTotalProgress = 0; - const progressBar = new progress("Upload progress:[:bar] :percent :etas", { - complete: "=", - incomplete: " ", - width: 50, - total: 100, - }); - - const uploadProgress = (currentProgress: number): void => { - progressBar.tick(currentProgress - lastTotalProgress); - lastTotalProgress = currentProgress; - }; + const uploadProgress = createUploadProgress(); try { await sdk.isAuthenticated(true); @@ -1636,19 +1592,7 @@ const doNativeRelease = async (releaseCommand: cli.IReleaseReactCommand): Promis buildNumber: releaseCommand.buildNumber, }; - let lastTotalProgress = 0; - - const progressBar = new progress("Upload progress:[:bar] :percent :etas", { - complete: "=", - incomplete: " ", - width: 50, - total: 100, - }); - - const uploadProgress = (currentProgress: number): void => { - progressBar.tick(currentProgress - lastTotalProgress); - lastTotalProgress = currentProgress; - }; + const uploadProgress = createUploadProgress(); try { await sdk.isAuthenticated(true); @@ -1679,32 +1623,6 @@ async function rollback(command: cli.IRollbackCommand): Promise { log('Successfully performed a rollback on the "' + command.deploymentName + '" deployment of the "' + command.appName + '" app.'); } -function requestAccessKey(): Promise { - return new Promise((resolve): void => { - prompt.message = ""; - prompt.delimiter = ""; - - prompt.start(); - - prompt.get( - { - properties: { - response: { - description: chalk.cyan("Enter your access key: "), - }, - }, - }, - (err: any, result: any): void => { - if (err) { - resolve(null); - } else { - resolve(result.response.trim()); - } - } - ); - }); -} - export const runReactNativeBundleCommand = async ( { platform, development, outputFolder, bundleName, packagerSourceMap, extraBundlerOptions }: BundleOptions, entryFile: string, diff --git a/script/index.ts b/script/index.ts deleted file mode 100644 index 706935d..0000000 --- a/script/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import AccountManager = require("./management-sdk"); -export = AccountManager; diff --git a/script/react-native-utils.ts b/script/react-native-utils.ts index 4d5b597..41a8a51 100644 --- a/script/react-native-utils.ts +++ b/script/react-native-utils.ts @@ -8,7 +8,8 @@ import { findHermesc, isBaseBytecodeCompatible } from "./utils/hermes-utils"; import { restoreSourceMapMetadata } from "./utils/source-map-utils"; import * as dotenv from "dotenv"; import * as cli from "./types/cli"; -import { log, sdk } from "./command-executor"; +import { log } from "./ui/log"; +import type AccountManager = require("./management-sdk"); export function isValidVersion(version: string): boolean { return !!valid(version) || /^\d+\.\d+$/.test(version); @@ -46,6 +47,7 @@ export function getSourceMapPaths( type BaseRelease = Pick; export async function takeHermesBaseBytecode( + sdk: Pick, { appName, deploymentName, appStoreVersion, buildNumber }: BaseRelease, baseReleaseTmpFolder: string, outputFolder: string, diff --git a/script/types.ts b/script/types/index.ts similarity index 93% rename from script/types.ts rename to script/types/index.ts index d849ca6..87094f1 100644 --- a/script/types.ts +++ b/script/types/index.ts @@ -14,7 +14,7 @@ export { AccessKey as ServerAccessKey, UpdateMetrics, BaseRelease, -} from "../script/types/rest-definitions"; +} from "./rest-definitions"; export interface CodePushError { message: string; diff --git a/script/ui/log.ts b/script/ui/log.ts new file mode 100644 index 0000000..b53ba29 --- /dev/null +++ b/script/ui/log.ts @@ -0,0 +1,3 @@ +export function log(message: unknown): void { + console.log(message); +} diff --git a/script/ui/progress.ts b/script/ui/progress.ts new file mode 100644 index 0000000..98b15be --- /dev/null +++ b/script/ui/progress.ts @@ -0,0 +1,17 @@ +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/prompt.ts new file mode 100644 index 0000000..0622080 --- /dev/null +++ b/script/ui/prompt.ts @@ -0,0 +1,62 @@ +import * as chalk from "chalk"; + +const prompt = require("prompt"); + +export const confirm = (message: string = "Are you sure?"): Promise => { + message += " (y/N):"; + return new Promise((resolve): void => { + prompt.message = ""; + prompt.delimiter = ""; + + prompt.start(); + + prompt.get( + { + properties: { + response: { + description: chalk.cyan(message), + }, + }, + }, + (err: any, result: any): void => { + const accepted = result.response && result.response.toLowerCase() === "y"; + const rejected = !result.response || result.response.toLowerCase() === "n"; + + if (accepted) { + resolve(true); + } else { + if (!rejected) { + console.log('Invalid response: "' + result.response + '"'); + } + resolve(false); + } + } + ); + }); +}; + +export function requestAccessKey(): Promise { + return new Promise((resolve): void => { + prompt.message = ""; + prompt.delimiter = ""; + + prompt.start(); + + prompt.get( + { + properties: { + response: { + description: chalk.cyan("Enter your access key: "), + }, + }, + }, + (err: any, result: any): void => { + if (err) { + resolve(null); + } else { + resolve(result.response.trim()); + } + } + ); + }); +} diff --git a/script/utils/hermes-utils.ts b/script/utils/hermes-utils.ts index 32d9d7a..fc98716 100644 --- a/script/utils/hermes-utils.ts +++ b/script/utils/hermes-utils.ts @@ -7,7 +7,7 @@ import * as chalk from "chalk"; import { coerce, gte } from "semver"; import * as cli from "../types/cli"; -import { log } from "../command-executor"; +import { log } from "../ui/log"; import { fileExists } from "./file-utils"; import { getGradleHermesCommand, getGradleHermesEnabled, readGradleProperty } from "./gradle-utils"; import { getReactNativePackagePath, getReactNativeVersion, getXcodeDotEnvValue } from "../react-native-utils"; diff --git a/test/cli.ts b/test/cli.ts index 026eed2..a1024c0 100644 --- a/test/cli.ts +++ b/test/cli.ts @@ -8,6 +8,8 @@ import * as path from "path"; import * as sinon from "sinon"; import * as cmdexec from "../script/command-executor"; import * as cli from "../script/types/cli"; +import * as uiLog from "../script/ui/log"; +import * as uiPrompt from "../script/ui/prompt"; 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"; @@ -99,9 +101,9 @@ describe("CLI", () => { beforeEach(() => { sinon.stub(cmdexec, "sdk").value(new SdkStub()); - confirm = sinon.stub(cmdexec, "confirm").resolves(true); + confirm = sinon.stub(uiPrompt, "confirm").resolves(true); sinon.stub(cmdexec, "createEmptyTempReleaseFolder").resolves(); - log = sinon.stub(cmdexec, "log"); + log = sinon.stub(uiLog, "log"); spawn = sinon.stub(cmdexec, "spawn").callsFake(fakeBundler); }); diff --git a/test/hermes-utils.ts b/test/hermes-utils.ts index b1590c2..08a046d 100644 --- a/test/hermes-utils.ts +++ b/test/hermes-utils.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 cmdexec from "../script/command-executor"; +import * as uiLog from "../script/ui/log"; import { findHermesc, getHermescBytecodeVersion, isBaseBytecodeCompatible, isHermesEnabled } from "../script/utils/hermes-utils"; import { restoreEnvAfterEach } from "./helpers/env"; import { useTempDirectory } from "./helpers/temp-directory"; @@ -108,7 +108,7 @@ describe("hermes-utils", () => { "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(cmdexec, "log"); + const log = sinon.stub(uiLog, "log"); assert.equal(await resolvedHermesc({ platform: "android" }), compiler); sinon.assert.notCalled(log); @@ -274,7 +274,7 @@ describe("hermes-utils", () => { let log: sinon.SinonStub; beforeEach(() => { - log = sinon.stub(cmdexec, "log"); + log = sinon.stub(uiLog, "log"); }); it("accepts the base bytecode when versions match", () => { diff --git a/test/release-size-flow.ts b/test/release-size-flow.ts index 458761c..079f08b 100644 --- a/test/release-size-flow.ts +++ b/test/release-size-flow.ts @@ -6,6 +6,7 @@ import * as path from "path"; import * as sinon from "sinon"; import * as cmdexec from "../script/command-executor"; import * as cli from "../script/types/cli"; +import * as uiLog from "../script/ui/log"; import { ASSET_OPTIMIZATION_DOCS_URL, MAX_ASSET_SIZE_BYTES, @@ -44,7 +45,7 @@ describe("command-executor release size checks", () => { isAuthenticated: sinon.stub().resolves(true), release: sdkRelease, }); - sinon.stub(cmdexec, "log"); + sinon.stub(uiLog, "log"); sinon.stub(console, "log"); warn = sinon.stub(console, "warn"); });