diff --git a/packages/cli/bin/binary-entry.ts b/packages/cli/bin/binary-entry.ts index eee5717f..773c596b 100644 --- a/packages/cli/bin/binary-entry.ts +++ b/packages/cli/bin/binary-entry.ts @@ -37,4 +37,4 @@ if (!process.stdin.isTTY || !process.stdout.isTTY) { process.env.CI = "true"; } -await runCLI(); +await runCLI({ distribution: "binary" }); diff --git a/packages/cli/src/cli/index.ts b/packages/cli/src/cli/index.ts index 8821542a..65fa89b2 100644 --- a/packages/cli/src/cli/index.ts +++ b/packages/cli/src/cli/index.ts @@ -6,11 +6,15 @@ import { readAuth } from "@/core/auth/index.js"; import { CLIExitError } from "./errors.js"; import { ErrorReporter } from "./telemetry/error-reporter.js"; import { addCommandInfoToErrorReporter } from "./telemetry/index.js"; -import type { CLIContext } from "./types.js"; +import type { CLIContext, Distribution } from "./types.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); -async function runCLI(): Promise { +interface RunCLIOptions { + distribution?: Distribution; +} + +async function runCLI(options?: RunCLIOptions): Promise { ensureNpmAssets(join(__dirname, "../assets")); // Create error reporter - single instance for the CLI session @@ -24,6 +28,7 @@ async function runCLI(): Promise { const context: CLIContext = { errorReporter, isNonInteractive, + distribution: options?.distribution ?? "npm", }; // Create program with injected context diff --git a/packages/cli/src/cli/types.ts b/packages/cli/src/cli/types.ts index 124ac9de..b72d30f6 100644 --- a/packages/cli/src/cli/types.ts +++ b/packages/cli/src/cli/types.ts @@ -1,6 +1,9 @@ import type { ErrorReporter } from "./telemetry/error-reporter.js"; +export type Distribution = "npm" | "binary"; + export interface CLIContext { errorReporter: ErrorReporter; isNonInteractive: boolean; + distribution: Distribution; } diff --git a/packages/cli/src/cli/utils/runCommand.ts b/packages/cli/src/cli/utils/runCommand.ts index fa6be349..99f4dc1f 100644 --- a/packages/cli/src/cli/utils/runCommand.ts +++ b/packages/cli/src/cli/utils/runCommand.ts @@ -3,7 +3,10 @@ import { login } from "@/cli/commands/auth/login-flow.js"; import type { CLIContext } from "@/cli/types.js"; import { printBanner } from "@/cli/utils/banner.js"; import { theme } from "@/cli/utils/theme.js"; -import { printUpgradeNotificationIfAvailable } from "@/cli/utils/upgradeNotification.js"; +import { + printUpgradeNotification, + startUpgradeCheck, +} from "@/cli/utils/upgradeNotification.js"; import { isLoggedIn, readAuth } from "@/core/auth/index.js"; import { isCLIError } from "@/core/errors.js"; import { initAppConfig } from "@/core/project/index.js"; @@ -76,7 +79,7 @@ export async function runCommand( } else { intro(theme.colors.base44OrangeBackground(" Base 44 ")); } - await printUpgradeNotificationIfAvailable(); + const upgradeCheckPromise = startUpgradeCheck(); try { // Check authentication if required @@ -105,6 +108,8 @@ export async function runCommand( } const result = await commandFn(); + await printUpgradeNotification(upgradeCheckPromise, context.distribution); + outro(result.outroMessage || ""); if (result.stdout) { diff --git a/packages/cli/src/cli/utils/upgradeNotification.ts b/packages/cli/src/cli/utils/upgradeNotification.ts index eaf3f173..912a31ab 100644 --- a/packages/cli/src/cli/utils/upgradeNotification.ts +++ b/packages/cli/src/cli/utils/upgradeNotification.ts @@ -1,22 +1,61 @@ -import { log } from "@clack/prompts"; +import { note } from "@clack/prompts"; +import type { Distribution } from "@/cli/types.js"; import { theme } from "@/cli/utils/theme.js"; import type { UpgradeInfo } from "@/cli/utils/version-check.js"; import { checkForUpgrade } from "@/cli/utils/version-check.js"; -function formatUpgradeMessage(info: UpgradeInfo): string { +type InstallMethod = "npm" | "brew" | "binary"; + +function detectInstallMethod(distribution: Distribution): InstallMethod { + if (distribution !== "binary") { + return "npm"; + } + const execPath = process.execPath.toLowerCase(); + if (execPath.includes("/homebrew/") || execPath.includes("/cellar/")) { + return "brew"; + } + return "binary"; +} + +function getUpgradeInstruction(method: InstallMethod): string { + switch (method) { + case "npm": + return "Run: npm install -g base44@latest"; + case "brew": + return "Run: brew upgrade base44"; + case "binary": + return "Download Base44 CLI from: https://github.com/base44/cli/releases/latest"; + } +} + +export function startUpgradeCheck(): Promise { + return checkForUpgrade().catch(() => null); +} + +function formatUpgradeMessage( + info: UpgradeInfo, + distribution: Distribution, +): string { const { shinyOrange } = theme.colors; const { bold } = theme.styles; + const instruction = getUpgradeInstruction(detectInstallMethod(distribution)); - return `${shinyOrange("Update available!")} ${shinyOrange(`${info.currentVersion} → ${info.latestVersion}`)} ${shinyOrange("Run:")} ${bold(shinyOrange("npm install -g base44@latest"))}`; + return [ + shinyOrange( + `Update available! ${info.currentVersion} → ${bold(info.latestVersion)}`, + ), + shinyOrange(instruction), + ].join("\n"); } -export async function printUpgradeNotificationIfAvailable(): Promise { +export async function printUpgradeNotification( + upgradeCheckPromise: Promise, + distribution: Distribution, +): Promise { try { - const upgradeInfo = await checkForUpgrade(); + const upgradeInfo = await upgradeCheckPromise; if (upgradeInfo) { - log.message(formatUpgradeMessage(upgradeInfo)); + note(formatUpgradeMessage(upgradeInfo, distribution)); } - } catch { - // Silently ignore errors - } + } catch {} } diff --git a/packages/cli/tests/cli/version-check.spec.ts b/packages/cli/tests/cli/version-check.spec.ts index 66cb8708..e9f826f5 100644 --- a/packages/cli/tests/cli/version-check.spec.ts +++ b/packages/cli/tests/cli/version-check.spec.ts @@ -13,7 +13,6 @@ describe("upgrade notification", () => { t.expectResult(result).toSucceed(); t.expectResult(result).toContain("Update available!"); t.expectResult(result).toContain("1.0.0"); - t.expectResult(result).toContain("npm install -g base44@latest"); }); it("does not display notification when version is current", async () => {