diff --git a/cli/package.json b/cli/package.json index 7af6b66d8..0aa73751b 100644 --- a/cli/package.json +++ b/cli/package.json @@ -18,8 +18,6 @@ "ntt": "src/index.ts" }, "dependencies": { - "chalk": "^5.6.0", - "ora": "8.2.0", "yargs": "18.0.0" }, "overrides": { diff --git a/cli/src/colors.ts b/cli/src/colors.ts new file mode 100644 index 000000000..49db0df1c --- /dev/null +++ b/cli/src/colors.ts @@ -0,0 +1,24 @@ +/* Simple ANSI color utilities */ + +const RESET = "\x1b[0m"; + +const useColors = + process.env.NO_COLOR === undefined && + (process.env.FORCE_COLOR !== undefined || process.stdout.isTTY); + +const c = (code: string) => (text: unknown) => + useColors ? `${code}${text}${RESET}` : String(text); + +export const colors = { + red: c("\x1b[31m"), + green: c("\x1b[32m"), + yellow: c("\x1b[33m"), + blue: c("\x1b[34m"), + cyan: c("\x1b[36m"), + white: c("\x1b[37m"), + gray: c("\x1b[90m"), + dim: c("\x1b[2m"), + reset: (text: unknown) => String(text), +}; + +export default colors; diff --git a/cli/src/configuration.ts b/cli/src/configuration.ts index 862c09a17..40baa85ed 100644 --- a/cli/src/configuration.ts +++ b/cli/src/configuration.ts @@ -2,7 +2,7 @@ import { assertChain, chains, type Chain } from "@wormhole-foundation/sdk"; import * as yargs from "yargs"; import fs from "fs"; import { ensureNttRoot } from "."; -import chalk from "chalk"; +import { colors } from "./colors.js"; // We support project-local and global configuration. // The configuration is stored in JSON files in $HOME/.ntt-cli/config.json (global) and .ntt-cli/config.json (local). @@ -189,13 +189,13 @@ export function get( const varName = envVarName(chain, key); const env = process.env[varName]; if (env) { - console.info(chalk.yellow(`Using ${varName} for ${chain} ${key}`)); + console.info(colors.yellow(`Using ${varName} for ${chain} ${key}`)); return env; } const local = getChainConfig("local", chain, key); if (local) { console.info( - chalk.yellow( + colors.yellow( `Using local configuration for ${chain} ${key} (in .ntt-cli/config.json)` ) ); @@ -204,7 +204,7 @@ export function get( const global = getChainConfig("global", chain, key); if (global) { console.info( - chalk.yellow( + colors.yellow( `Using global configuration for ${chain} ${key} (in $HOME/.ntt-cli/config.json)` ) ); diff --git a/cli/src/diff.ts b/cli/src/diff.ts index 5df51a555..2daea18e3 100644 --- a/cli/src/diff.ts +++ b/cli/src/diff.ts @@ -1,4 +1,4 @@ -import chalk from "chalk"; +import { colors } from "./colors.js"; export type Diff = { push?: T; @@ -94,9 +94,9 @@ export function colorizeDiff(diff: any, indent = 2): string { if (push !== undefined && pull !== undefined) { result += `${line}\n`; } else if (push !== undefined) { - result += line.replace(trimmedLine, chalk.red(trimmedLine)) + "\n"; + result += line.replace(trimmedLine, colors.red(trimmedLine)) + "\n"; } else if (pull !== undefined) { - result += line.replace(trimmedLine, chalk.green(trimmedLine)) + "\n"; + result += line.replace(trimmedLine, colors.green(trimmedLine)) + "\n"; } } else { result += line + "\n"; @@ -105,7 +105,7 @@ export function colorizeDiff(diff: any, indent = 2): string { trimmedLine.startsWith('"push"') || trimmedLine.startsWith('"pull"') ) { - const color = trimmedLine.startsWith('"push"') ? chalk.green : chalk.red; + const color = trimmedLine.startsWith('"push"') ? colors.green : colors.red; result += line.replace(trimmedLine, color(trimmedLine)) + "\n"; } else { result += line + "\n"; diff --git a/cli/src/error.ts b/cli/src/error.ts index 790616eaf..435144382 100644 --- a/cli/src/error.ts +++ b/cli/src/error.ts @@ -1,4 +1,4 @@ -import chalk from "chalk"; +import { colors } from "./colors.js"; import type { Chain, Network } from "@wormhole-foundation/sdk"; import { chainToPlatform } from "@wormhole-foundation/sdk-base"; @@ -55,7 +55,7 @@ function handleSuiDeploymentError(error: any, rpc: string): boolean { return false; } - console.error(chalk.red("\nSui deployment failed\n")); + console.error(colors.red("\nSui deployment failed\n")); let errorMessage = ""; @@ -82,7 +82,7 @@ function handleSuiDeploymentError(error: any, rpc: string): boolean { errorMessage = error.message; } - console.error(chalk.red(errorMessage || "Unknown deployment error")); + console.error(colors.red(errorMessage || "Unknown deployment error")); return true; } @@ -106,27 +106,27 @@ function handleRpcConnectionError( const errorMessage = error?.message || String(error); - console.error(chalk.red(`RPC connection error for ${chain} on ${network}\n`)); - console.error(chalk.yellow("RPC endpoint:"), chalk.white(rpc)); - console.error(chalk.yellow("Error:"), errorMessage); + console.error(colors.red(`RPC connection error for ${chain} on ${network}\n`)); + console.error(colors.yellow("RPC endpoint:"), colors.white(rpc)); + console.error(colors.yellow("Error:"), errorMessage); console.error(); console.error( - chalk.yellow( + colors.yellow( "This error usually means the RPC endpoint is missing, invalid, or unreachable." ) ); console.error( - chalk.yellow( + colors.yellow( "You can specify a private RPC endpoint by creating an overrides.json file.\n" ) ); console.error( - chalk.cyan("Create a file named ") + - chalk.white("overrides.json") + - chalk.cyan(" in your project root:") + colors.cyan("Create a file named ") + + colors.white("overrides.json") + + colors.cyan(" in your project root:") ); console.error( - chalk.white(` + colors.white(` { "chains": { "${chain}": { @@ -142,7 +142,7 @@ function handleRpcConnectionError( const platform = chainToPlatform(chain as any); if (platform === "Evm") { console.error( - chalk.cyan(`Find RPC endpoints for ${chain}: https://chainlist.org`) + colors.cyan(`Find RPC endpoints for ${chain}: https://chainlist.org`) ); } } catch (e) { @@ -150,7 +150,7 @@ function handleRpcConnectionError( } console.error( - chalk.cyan( + colors.cyan( `For more information about overrides.json:\n` + ` • https://wormhole.com/docs/products/token-transfers/native-token-transfers/faqs/#how-can-i-specify-a-custom-rpc-for-ntt` ) @@ -163,30 +163,30 @@ function handleRpcConnectionError( * @param error - The error that occurred */ function handleGenericError(error: any): never { - console.error(chalk.red("\nDeployment failed\n")); + console.error(colors.red("\nDeployment failed\n")); const errorMessage = error?.message || String(error); // Show stdout if available if (error.stdout) { - console.error(chalk.yellow("Output:")); + console.error(colors.yellow("Output:")); console.error(error.stdout.toString()); } // Show stderr if available if (error.stderr) { - console.error(chalk.yellow("\nError output:")); + console.error(colors.yellow("\nError output:")); console.error(error.stderr.toString()); } // Show message if no stdout/stderr if (!error.stdout && !error.stderr) { - console.error(chalk.yellow("Error:"), errorMessage); + console.error(colors.yellow("Error:"), errorMessage); // Show stack trace for debugging if available if (error.stack) { - console.error(chalk.dim("\nStack trace:")); - console.error(chalk.dim(error.stack)); + console.error(colors.dim("\nStack trace:")); + console.error(colors.dim(error.stack)); } } @@ -223,6 +223,6 @@ export function logRpcError( return; } const message = error instanceof Error ? error.message : String(error); - console.error(chalk.red(`RPC error for ${chain} on ${network}`)); + console.error(colors.red(`RPC error for ${chain} on ${network}`)); console.error(message); } diff --git a/cli/src/index.ts b/cli/src/index.ts index 0bce03bc0..c40cfb2af 100755 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -12,7 +12,7 @@ import { import { execSync } from "child_process"; import * as myEvmSigner from "./evmsigner.js"; -import chalk from "chalk"; +import { colors } from "./colors.js"; import yargs from "yargs"; import { $ } from "bun"; import { hideBin } from "yargs/helpers"; @@ -120,7 +120,7 @@ import { newSignSendWaiter, signSendWaitWithOverride } from "./signSendWait.js"; const overrides: WormholeConfigOverrides = (function () { // read overrides.json file if exists if (fs.existsSync("overrides.json")) { - console.error(chalk.yellow("Using overrides.json")); + console.error(colors.yellow("Using overrides.json")); return JSON.parse(fs.readFileSync("overrides.json").toString()); } else { return {}; @@ -721,31 +721,31 @@ yargs(hideBin(process.argv)) // HyperEVM confirmation if (chain === "HyperEVM" && !argv["yes"]) { - console.log(chalk.yellow("⚠️ HyperEVM Deployment Requirements:")); + console.log(colors.yellow("⚠️ HyperEVM Deployment Requirements:")); console.log( - chalk.yellow( + colors.yellow( "Before proceeding with the HyperEVM deployment, please ensure:" ) ); console.log( - chalk.yellow( + colors.yellow( "1. You have created a verified account by depositing into Hyperliquid from the deployer wallet" ) ); console.log( - chalk.white("Hyperliquid app: https://app.hyperliquid.xyz/") + colors.white("Hyperliquid app: https://app.hyperliquid.xyz/") ); console.log( - chalk.yellow( + colors.yellow( "2. You have enabled larger blocks to be used for the deployment" ) ); console.log( - chalk.white( + colors.white( "Docs: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/dual-block-architecture" ) ); - console.log(chalk.yellow("")); + console.log(colors.yellow("")); await askForConfirmation( "Did you make sure to do steps 1 & 2 mentioned above?" @@ -790,7 +790,7 @@ yargs(hideBin(process.argv)) overrides ); - console.log("token decimals:", chalk.yellow(decimals)); + console.log("token decimals:", colors.yellow(decimals)); config.transceivers.wormhole.executor = argv["executor"]; @@ -1098,9 +1098,9 @@ yargs(hideBin(process.argv)) process.exit(1); } fs.writeFileSync(path, JSON.stringify(deployment, null, 2)); - console.log(chalk.green(`${path} created — this file stores your NTT deployment configuration`)); + console.log(colors.green(`${path} created — this file stores your NTT deployment configuration`)); console.log( - chalk.cyan( + colors.cyan( `\nTip: To use custom RPC endpoints, rename example-overrides.json to overrides.json and edit as needed.` ) ); @@ -1139,7 +1139,7 @@ yargs(hideBin(process.argv)) EXCLUDED_DIFF_PATHS ); if (Object.keys(diff).length !== 0) { - console.error(chalk.reset(colorizeDiff({ [chain]: diff }))); + console.error(colors.reset(colorizeDiff({ [chain]: diff }))); changed = true; // Preserve excluded fields from local config when pulling const preservedConfig = { ...deployment.config.remote! }; @@ -1443,7 +1443,7 @@ yargs(hideBin(process.argv)) const diff = diffObjects(local!, remote!, EXCLUDED_DIFF_PATHS); if (Object.keys(diff).length !== 0) { - console.error(chalk.reset(colorizeDiff({ [chain]: diff }))); + console.error(colors.reset(colorizeDiff({ [chain]: diff }))); fixable++; } @@ -1460,7 +1460,7 @@ yargs(hideBin(process.argv)) } if (Object.keys(extraInfo).length > 0) { - console.log(chalk.yellow(JSON.stringify(extraInfo, null, 2))); + console.log(colors.yellow(JSON.stringify(extraInfo, null, 2))); } // verify peers @@ -1487,13 +1487,13 @@ yargs(hideBin(process.argv)) ] of missingConfig.standardRelaying) { if (shouldBeSet) { console.warn( - chalk.yellow( + colors.yellow( ` Standard relaying not configured for ${relayingTarget}` ) ); } else { console.warn( - chalk.yellow( + colors.yellow( ` Standard relaying configured for ${relayingTarget}, but should not be` ) ); @@ -1505,13 +1505,13 @@ yargs(hideBin(process.argv)) ] of missingConfig.specialRelaying) { if (shouldBeSet) { console.warn( - chalk.yellow( + colors.yellow( ` Special relaying not configured for ${relayingTarget}` ) ); } else { console.warn( - chalk.yellow( + colors.yellow( ` Special relaying configured for ${relayingTarget}, but should not be` ) ); @@ -1693,12 +1693,12 @@ yargs(hideBin(process.argv)) if (major === -1) { if (!argv["yes"]) { console.warn( - chalk.yellow( + colors.yellow( "SPL Multisig token mint authority is only supported for versions >= 3.x.x" ) ); console.warn( - chalk.yellow( + colors.yellow( "Ensure the program version you wish to deploy supports SPL Multisig token mint authority" ) ); @@ -2175,12 +2175,12 @@ yargs(hideBin(process.argv)) // undeployed -- assume version compatible via warning if (major === -1 && !argv["yes"]) { console.warn( - chalk.yellow( + colors.yellow( "SPL Multisig token mint authority is only supported for versions >= 3.x.x" ) ); console.warn( - chalk.yellow( + colors.yellow( "Ensure the program version you wish to deploy supports SPL Multisig token mint authority" ) ); @@ -2391,13 +2391,13 @@ yargs(hideBin(process.argv)) process.exit(1); } - console.log(chalk.blue("🔗 Manual setPeer Operation")); - console.log(`Source Chain: ${chalk.yellow(sourceChain)}`); - console.log(`Peer Chain: ${chalk.yellow(peerChain)}`); - console.log(`Peer Address: ${chalk.yellow(peerAddress)}`); - console.log(`Token Decimals: ${chalk.yellow(tokenDecimals)}`); + console.log(colors.blue("🔗 Manual setPeer Operation")); + console.log(`Source Chain: ${colors.yellow(sourceChain)}`); + console.log(`Peer Chain: ${colors.yellow(peerChain)}`); + console.log(`Peer Address: ${colors.yellow(peerAddress)}`); + console.log(`Token Decimals: ${colors.yellow(tokenDecimals)}`); console.log( - `Inbound Limit: ${chalk.yellow(inboundLimit.toString())}` + `Inbound Limit: ${colors.yellow(inboundLimit.toString())}` ); try { @@ -2413,7 +2413,7 @@ yargs(hideBin(process.argv)) ); console.log( - `\nSource NTT Manager: ${chalk.yellow(sourceConfig.manager)}` + `\nSource NTT Manager: ${colors.yellow(sourceConfig.manager)}` ); // Create peer address object @@ -2426,11 +2426,11 @@ yargs(hideBin(process.argv)) const signer = await getSigner(ctx, signerType); console.log( - `Signer Address: ${chalk.yellow( + `Signer Address: ${colors.yellow( signer.address.address.toString() )}` ); - console.log("\n" + chalk.blue("Executing setPeer transaction...")); + console.log("\n" + colors.blue("Executing setPeer transaction...")); // Call setPeer on the NTT instance (it returns an AsyncGenerator) const setPeerTxs = ntt.setPeer( @@ -2453,7 +2453,7 @@ yargs(hideBin(process.argv)) // Display transaction results console.log( - `Transaction Hash: ${chalk.green( + `Transaction Hash: ${colors.green( results[0]?.txid || results[0] || "Transaction completed" )}` ); @@ -2487,13 +2487,13 @@ yargs(hideBin(process.argv)) } console.log( - chalk.green("\n✅ setPeer operation completed successfully!") + colors.green("\n✅ setPeer operation completed successfully!") ); console.log( `Peer relationship established: ${sourceChain} ↔ ${peerChain}` ); } catch (error) { - console.error(chalk.red("\n❌ setPeer operation failed:")); + console.error(colors.red("\n❌ setPeer operation failed:")); console.error( "ERROR: Main error message:", error instanceof Error ? error.message : String(error) @@ -2602,15 +2602,15 @@ yargs(hideBin(process.argv)) process.exit(1); } - console.log(chalk.blue("💰 Manual Transfer Operation")); - console.log(`Source Chain: ${chalk.yellow(sourceChain)}`); - console.log(`Destination Chain: ${chalk.yellow(destinationChain)}`); + console.log(colors.blue("💰 Manual Transfer Operation")); + console.log(`Source Chain: ${colors.yellow(sourceChain)}`); + console.log(`Destination Chain: ${colors.yellow(destinationChain)}`); console.log( - `Destination Address: ${chalk.yellow(destinationAddress)}` + `Destination Address: ${colors.yellow(destinationAddress)}` ); - console.log(`Amount: ${chalk.yellow(amount.toString())}`); + console.log(`Amount: ${colors.yellow(amount.toString())}`); console.log( - `Queue if rate limited: ${chalk.yellow(queue.toString())}` + `Queue if rate limited: ${colors.yellow(queue.toString())}` ); try { @@ -2626,7 +2626,7 @@ yargs(hideBin(process.argv)) ); console.log( - `\nSource NTT Manager: ${chalk.yellow(sourceConfig.manager)}` + `\nSource NTT Manager: ${colors.yellow(sourceConfig.manager)}` ); // Create destination address object @@ -2639,11 +2639,11 @@ yargs(hideBin(process.argv)) const signer = await getSigner(ctx, signerType); console.log( - `Signer Address: ${chalk.yellow( + `Signer Address: ${colors.yellow( signer.address.address.toString() )}` ); - console.log("\n" + chalk.blue("Executing transfer transaction...")); + console.log("\n" + colors.blue("Executing transfer transaction...")); // Call transfer on the NTT instance (it returns an AsyncGenerator) const transferTxs = ntt.transfer( @@ -2667,7 +2667,7 @@ yargs(hideBin(process.argv)) // Display transaction results console.log( - `Transaction Hash: ${chalk.green( + `Transaction Hash: ${colors.green( results[0]?.txid || results[0] || "Transaction completed" )}` ); @@ -2689,12 +2689,12 @@ yargs(hideBin(process.argv)) } console.log( - chalk.green("\n✅ Transfer operation completed successfully!") + colors.green("\n✅ Transfer operation completed successfully!") ); console.log(`Transfer sent: ${sourceChain} → ${destinationChain}`); console.log(`Amount: ${amount.toString()} tokens`); } catch (error) { - console.error(chalk.red("\n❌ Transfer operation failed:")); + console.error(colors.red("\n❌ Transfer operation failed:")); console.error( "ERROR: Main error message:", error instanceof Error ? error.message : String(error) @@ -2746,7 +2746,7 @@ function checkConfigErrors( fatal++; } if (config.limits.outbound === formatNumber(0n, deployment.decimals)) { - console.warn(chalk.yellow(`${chain} has an outbound limit of 0`)); + console.warn(colors.yellow(`${chain} has an outbound limit of 0`)); } for (const [c, limit] of Object.entries(config.limits.inbound)) { if (!checkNumberFormatting(limit, deployment.decimals)) { @@ -2757,7 +2757,7 @@ function checkConfigErrors( } if (limit === formatNumber(0n, deployment.decimals)) { console.warn( - chalk.yellow(`${chain} has an inbound limit of 0 from ${c}`) + colors.yellow(`${chain} has an inbound limit of 0 from ${c}`) ); } } @@ -2776,7 +2776,7 @@ function createWorkTree(platform: Platform, version: string): string { if (fs.existsSync(worktreeName)) { console.log( - chalk.yellow( + colors.yellow( `Worktree already exists at ${worktreeName}. Resetting to ${tag}` ) ); @@ -2800,7 +2800,7 @@ function createWorkTree(platform: Platform, version: string): string { ); console.log( - chalk.green(`Created worktree at ${worktreeName} from tag ${tag}`) + colors.green(`Created worktree at ${worktreeName} from tag ${tag}`) ); return worktreeName; } @@ -4468,7 +4468,7 @@ async function deploySui( ); } - console.log(chalk.green("Sui NTT deployment completed successfully!")); + console.log(colors.green("Sui NTT deployment completed successfully!")); console.log(`NTT Package ID: ${nttPackageId}`); console.log(`NTT State ID: ${nttStateId}`); console.log(`Wormhole Transceiver Package ID: ${whTransceiverPackageId}`); @@ -4693,7 +4693,7 @@ async function pushDeployment( const canonical = canonicalAddress(deployment.manager); console.log(`Pushing changes to ${deployment.manager.chain} (${canonical})`); - console.log(chalk.reset(colorizeDiff(diff))); + console.log(colors.reset(colorizeDiff(diff))); if (!yes) { await askForConfirmation(); } @@ -5304,7 +5304,7 @@ function checkSolanaVersion(pwd: string): void { if (!versionMatch) { console.warn( - chalk.yellow("Warning: Could not find solana_version in Anchor.toml") + colors.yellow("Warning: Could not find solana_version in Anchor.toml") ); return; } @@ -5321,7 +5321,7 @@ function checkSolanaVersion(pwd: string): void { }); const versionMatch = output.match(/solana-cli (\d+\.\d+\.\d+)/); if (!versionMatch) { - console.error(chalk.red("Error: Could not parse solana CLI version")); + console.error(colors.red("Error: Could not parse solana CLI version")); process.exit(1); } currentVersion = versionMatch[1]; @@ -5337,12 +5337,12 @@ function checkSolanaVersion(pwd: string): void { } } catch (error) { console.error( - chalk.red( + colors.red( "Error: solana CLI not found. Please install the Solana toolchain." ) ); console.error( - chalk.yellow( + colors.yellow( 'Install with: sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"' ) ); @@ -5350,12 +5350,12 @@ function checkSolanaVersion(pwd: string): void { } if (currentVersion !== requiredVersion) { - console.log(chalk.yellow(`Solana version mismatch detected:`)); + console.log(colors.yellow(`Solana version mismatch detected:`)); console.log( - chalk.yellow(` Required: ${requiredVersion} (from Anchor.toml)`) + colors.yellow(` Required: ${requiredVersion} (from Anchor.toml)`) ); - console.log(chalk.yellow(` Current: ${currentVersion}`)); - console.log(chalk.yellow(`\nSwitching to required version...`)); + console.log(colors.yellow(` Current: ${currentVersion}`)); + console.log(colors.yellow(`\nSwitching to required version...`)); // Run the appropriate version switch command const installCommand = @@ -5366,24 +5366,24 @@ function checkSolanaVersion(pwd: string): void { try { execSync(installCommand, { stdio: "inherit" }); console.log( - chalk.green( + colors.green( `Successfully switched to Solana version ${requiredVersion}` ) ); } catch (error) { console.error( - chalk.red(`Failed to switch Solana version using ${installCommand}`) + colors.red(`Failed to switch Solana version using ${installCommand}`) ); - console.error(chalk.red(`Please run manually: ${installCommand}`)); + console.error(colors.red(`Please run manually: ${installCommand}`)); process.exit(1); } } } catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") { - console.warn(chalk.yellow("Warning: Could not read Anchor.toml file")); + console.warn(colors.yellow("Warning: Could not read Anchor.toml file")); } else { console.warn( - chalk.yellow( + colors.yellow( `Warning: Failed to check Solana version: ${ error instanceof Error ? error.message : error }` @@ -5401,7 +5401,7 @@ function checkAnchorVersion(pwd: string) { if (!versionMatch) { console.error( - chalk.red("Error: Could not find anchor_version in Anchor.toml") + colors.red("Error: Could not find anchor_version in Anchor.toml") ); process.exit(1); } @@ -5423,22 +5423,22 @@ function checkAnchorVersion(pwd: string) { // version looks like "anchor-cli 0.14.0" const [_, v] = version.split(" "); if (v !== expected) { - console.error(chalk.red(`Anchor CLI version mismatch!`)); - console.error(chalk.red(` Required: ${expected} (from Anchor.toml)`)); - console.error(chalk.red(` Current: ${v}`)); + console.error(colors.red(`Anchor CLI version mismatch!`)); + console.error(colors.red(` Required: ${expected} (from Anchor.toml)`)); + console.error(colors.red(` Current: ${v}`)); console.error( - chalk.yellow(`\nTo fix this, install the correct version of Anchor`) + colors.yellow(`\nTo fix this, install the correct version of Anchor`) ); console.error( - chalk.gray("See https://www.anchor-lang.com/docs/installation") + colors.gray("See https://www.anchor-lang.com/docs/installation") ); process.exit(1); } } catch (error) { if (error instanceof Error && "code" in error && error.code === "ENOENT") { - console.error(chalk.red("Error: Could not read Anchor.toml file")); + console.error(colors.red("Error: Could not read Anchor.toml file")); console.error( - chalk.yellow(`Expected file at: ${pwd}/solana/Anchor.toml`) + colors.yellow(`Expected file at: ${pwd}/solana/Anchor.toml`) ); process.exit(1); } else { @@ -5475,17 +5475,17 @@ function resolveVersion( function warnLocalDeployment(yes: boolean): Promise { if (!yes) { console.warn( - chalk.yellow( + colors.yellow( "WARNING: You are deploying from your local working directory." ) ); console.warn( - chalk.yellow( + colors.yellow( "This bypasses version control and may deploy untested changes." ) ); console.warn( - chalk.yellow( + colors.yellow( "Ensure your local changes are thoroughly tested and compatible." ) ); diff --git a/cli/src/solanaHelpers.ts b/cli/src/solanaHelpers.ts index 5a0eb735f..374c7a16a 100644 --- a/cli/src/solanaHelpers.ts +++ b/cli/src/solanaHelpers.ts @@ -1,4 +1,4 @@ -import chalk from "chalk"; +import { colors } from "./colors.js"; import { signSendWait, type AccountAddress, chainToPlatform } from "@wormhole-foundation/sdk"; import type { SolanaChains } from "@wormhole-foundation/sdk-solana"; import type { SolanaNtt } from "@wormhole-foundation/sdk-solana-ntt"; @@ -17,5 +17,5 @@ export async function registerSolanaTransceiver( owner: signer.address.address as any as AccountAddress, }); await signSendWait(ctx, registerTx, signer.signer as any); - console.log(chalk.green("Wormhole transceiver registered successfully")); + console.log(colors.green("Wormhole transceiver registered successfully")); } \ No newline at end of file diff --git a/cli/src/tokenTransfer.ts b/cli/src/tokenTransfer.ts index 676f4c8af..5c61c7477 100644 --- a/cli/src/tokenTransfer.ts +++ b/cli/src/tokenTransfer.ts @@ -1,8 +1,7 @@ // NOTE: We rely on the Wormhole TypeScript SDK for cross-chain execution logic: // https://github.com/wormhole-foundation/wormhole-sdk-ts -import chalk from "chalk"; -import ora, { type Ora } from "ora"; +import { colors } from "./colors.js"; import type { Argv, CommandModule } from "yargs"; import { Wormhole, @@ -274,11 +273,11 @@ async function executeTokenTransfer( } if (payerPath && chainToPlatform(sourceChainInput) !== "Solana") { - console.warn( - chalk.yellow( - "--payer is only used when the source chain is Solana. Ignoring provided path." - ) - ); +console.warn( + colors.yellow( + "--payer is only used when the source chain is Solana. Ignoring provided path." + ) + ); } const rpcRaw = argv["rpc"]; @@ -505,7 +504,7 @@ async function executeTokenTransfer( applyMsgValueOverride(executorConfig, destinationTokenId, msgValueToUse); if (destinationMsgValueOverride === undefined) { console.warn( - chalk.yellow( + colors.yellow( `Destination ${destinationChainInput} requires msgValue funding for the Wormhole Executor. Using default ${msgValueToUse.toString()} lamports. Pass --destination-msg-value to override.` ) ); @@ -516,7 +515,7 @@ async function executeTokenTransfer( } } else if (destinationMsgValueOverride !== undefined) { console.warn( - chalk.yellow( + colors.yellow( `--destination-msg-value is only required for SVM destinations. Ignoring override for ${destinationChainInput}.` ) ); @@ -577,7 +576,7 @@ async function executeTokenTransfer( if (quoteResult.warnings?.length) { for (const warning of quoteResult.warnings) { - console.warn(chalk.yellow(formatQuoteWarning(warning))); + console.warn(colors.yellow(formatQuoteWarning(warning))); } } @@ -589,21 +588,21 @@ async function executeTokenTransfer( `Transferring ${formattedTransferAmount} tokens from ${sourceChainInput} to ${destinationChainInput} (${network})` ); console.log( - `Source address: ${chalk.cyan(sourceSigner.address.address.toString())}` + `Source address: ${colors.cyan(sourceSigner.address.address.toString())}` ); const destinationAddressDisplay = Wormhole.canonicalAddress( destinationAddress ); console.log( - `Destination address: ${chalk.cyan(destinationAddressDisplay)}` + `Destination address: ${colors.cyan(destinationAddressDisplay)}` ); console.log( - `Source token: ${chalk.cyan( + `Source token: ${colors.cyan( sourceDeployment.token )} (decimals: ${decimals.toString()})` ); console.log( - `Destination token: ${chalk.cyan(destinationDeployment.token)}` + `Destination token: ${colors.cyan(destinationDeployment.token)}` ); console.log( `Estimated destination amount: ${estimatedDestinationAmount}` @@ -702,7 +701,7 @@ async function executeTokenTransfer( index === 0 ? "Source transaction" : `Source transaction #${index + 1}`; - console.log(`${label}: ${chalk.cyan(tx.txid.toString())}`); + console.log(`${label}: ${colors.cyan(tx.txid.toString())}`); }); } @@ -719,7 +718,7 @@ async function executeTokenTransfer( ); if (vaa) { console.log( - `Attestation sequence: ${chalk.cyan(vaa.sequence.toString())}` + `Attestation sequence: ${colors.cyan(vaa.sequence.toString())}` ); } } catch (error) { @@ -736,7 +735,7 @@ async function executeTokenTransfer( } console.log( - chalk.green( + colors.green( "Transfer submitted. The Wormhole Executor will relay the transfer automatically once finalized." ) ); @@ -892,10 +891,10 @@ async function confirmMainnetTransfer( platform === "Solana" ? "lamports" : "base units"; const prompt = [ "", - chalk.yellow( + colors.yellow( `You are about to submit a Mainnet transfer of ${formattedAmount} tokens from ${sourceChain} to ${destinationChain}.` ), - chalk.yellow( + colors.yellow( `Confirm this amount is expressed in human-readable units (not ${unitDescriptor}).` ), "Type \"yes\" (or \"y\") to continue: ", @@ -1061,13 +1060,13 @@ function isUnsupportedSuiDestinationError(error: unknown): boolean { */ function reportTokenTransferError(error: unknown): void { if (error instanceof TokenTransferError) { - console.error(chalk.red(error.message)); + console.error(colors.red(error.message)); if (error.cause) { console.error(stringifyError(error.cause)); } return; } - console.error(chalk.red("Unexpected token-transfer error")); + console.error(colors.red("Unexpected token-transfer error")); console.error(stringifyError(error)); } @@ -1217,7 +1216,7 @@ function applyRpcOverrides( const chain = chainName as Chain; if (!allowedChains.has(chain)) { console.warn( - chalk.yellow( + colors.yellow( `Warning: RPC override provided for ${chain}, which is not part of this transfer.` ) ); @@ -1234,15 +1233,14 @@ function applyRpcOverrides( } /** - * Intercepts console output to show a spinner when a retriable log message appears. + * Intercepts console output to suppress retry messages while showing status. */ async function withRetryStatus( needle: string | RegExp, fn: () => Promise ): Promise { const originalLog = console.log; - let sawNeedle = false; - let spinner: Ora | null = null; + let lastMessage = ""; const matchesNeedle = (message: string): boolean => { if (typeof needle === "string") { @@ -1256,34 +1254,29 @@ async function withRetryStatus( return result; }; - const stopSpinner = (): void => { - if (!spinner) { - return; - } - spinner.stop(); - spinner = null; - }; - console.log = (...args: Parameters) => { const message = args.map(String).join(" "); if (matchesNeedle(message)) { - sawNeedle = true; - if (!spinner) { - spinner = ora({ text: message }).start(); - } else { - spinner.text = message; + // Show retry status inline (overwrite previous line if TTY) + if (process.stdout.isTTY && lastMessage) { + process.stdout.write(`\r${message}`.padEnd(lastMessage.length + 1)); + } else if (!lastMessage) { + originalLog(message); } + lastMessage = message; return; } - stopSpinner(); + if (lastMessage && process.stdout.isTTY) { + process.stdout.write("\n"); + lastMessage = ""; + } originalLog(...args); }; try { return await fn(); } finally { - stopSpinner(); - if (sawNeedle && process.stdout.isTTY) { + if (lastMessage && process.stdout.isTTY) { process.stdout.write("\n"); } console.log = originalLog;