From 8eb194c03efd0f7c9b1e865c5d7fb3c972a1ca92 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Tue, 17 Mar 2026 14:23:33 +0200 Subject: [PATCH 1/7] refactor logger to simple logger --- docs/commands.md | 18 ++-- packages/cli/src/cli/commands/agents/pull.ts | 14 +-- packages/cli/src/cli/commands/agents/push.ts | 16 +-- .../cli/src/cli/commands/auth/login-flow.ts | 12 ++- packages/cli/src/cli/commands/auth/login.ts | 4 +- .../cli/commands/connectors/list-available.ts | 10 +- .../cli/commands/connectors/oauth-prompt.ts | 14 ++- .../cli/src/cli/commands/connectors/pull.ts | 14 +-- .../cli/src/cli/commands/connectors/push.ts | 35 +++--- .../cli/src/cli/commands/entities/push.ts | 16 +-- .../cli/src/cli/commands/functions/deploy.ts | 41 ++++--- .../commands/functions/formatDeployResult.ts | 13 ++- .../cli/src/cli/commands/functions/list.ts | 10 +- .../cli/src/cli/commands/functions/pull.ts | 17 ++- .../cli/src/cli/commands/project/create.ts | 101 ++++++++++-------- .../cli/src/cli/commands/project/deploy.ts | 39 ++++--- .../cli/src/cli/commands/project/eject.ts | 20 ++-- packages/cli/src/cli/commands/project/link.ts | 12 ++- packages/cli/src/cli/commands/secrets/list.ts | 10 +- packages/cli/src/cli/commands/secrets/set.ts | 15 ++- packages/cli/src/cli/index.ts | 4 + packages/cli/src/cli/types.ts | 2 + .../src/cli/utils/command/Base44Command.ts | 12 ++- .../cli/src/cli/utils/command/middleware.ts | 11 +- packages/cli/src/cli/utils/index.ts | 1 + .../cli/src/cli/utils/logger/ClackLogger.ts | 27 +++++ .../cli/src/cli/utils/logger/SimpleLogger.ts | 26 +++++ packages/cli/src/cli/utils/logger/index.ts | 3 + packages/cli/src/cli/utils/logger/types.ts | 15 +++ 29 files changed, 358 insertions(+), 174 deletions(-) create mode 100644 packages/cli/src/cli/utils/logger/ClackLogger.ts create mode 100644 packages/cli/src/cli/utils/logger/SimpleLogger.ts create mode 100644 packages/cli/src/cli/utils/logger/index.ts create mode 100644 packages/cli/src/cli/utils/logger/types.ts diff --git a/docs/commands.md b/docs/commands.md index 42b55203..c31149cd 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,6 +1,6 @@ # Adding & Modifying CLI Commands -**Keywords:** command, factory pattern, Base44Command, isNonInteractive, runTask, spinner, theming, chalk, program.ts, register, banner, intro, outro +**Keywords:** command, factory pattern, Base44Command, isNonInteractive, Logger, runTask, spinner, theming, chalk, program.ts, register, banner, intro, outro Commands live in `src/cli/commands//`. They use a **factory pattern** — each file exports a function that returns a `Base44Command`. @@ -8,12 +8,12 @@ Commands live in `src/cli/commands//`. They use a **factory pattern** ```typescript // src/cli/commands//.ts -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask, theme } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; -async function myAction(): Promise { +async function myAction(logger: Logger): Promise { const result = await runTask( "Doing something...", async () => { @@ -26,7 +26,7 @@ async function myAction(): Promise { } ); - log.success("Operation completed!"); + logger.success("Operation completed!"); return { outroMessage: `Created ${theme.styles.bold(result.name)}` }; } @@ -35,7 +35,9 @@ export function getMyCommand(): Command { return new Base44Command("") .description("") .option("-f, --flag", "Some flag") - .action(myAction); + .action((_options: unknown, command: Base44Command) => + myAction(command.logger), + ); } ``` @@ -44,6 +46,7 @@ export function getMyCommand(): Command { - Use `Base44Command` class - Commands must NOT call `intro()` or `outro()` directly - The action function must return `RunCommandResult` with an `outroMessage` +- Use `command.logger` for output — never import `log` from `@clack/prompts` directly ## Base44Command Options @@ -83,11 +86,13 @@ export interface CLIContext { errorReporter: ErrorReporter; isNonInteractive: boolean; distribution: Distribution; + logger: Logger; } ``` - Created once in `runCLI()` at startup - `isNonInteractive` is `true` when stdin/stdout are not a TTY (e.g., CI, piped output, AI agents). Controls quiet mode — when true, all clack UI is suppressed. +- `logger` is a `Logger` instance — `ClackLogger` in interactive mode, `SimpleLogger` in non-interactive mode. Commands access it via `command.logger`. ### Using `isNonInteractive` @@ -153,7 +158,7 @@ const result = await runTask( ); ``` -Avoid manual try/catch with `log.message` for async operations -- use `runTask()` instead. +Avoid manual try/catch with `logger.message` for async operations -- use `runTask()` instead. ### Subprocess Logging @@ -224,6 +229,7 @@ Access `command.args` for positional arguments and `command.opts()` for options - **Command factory pattern** - Commands export `getXCommand()` functions (no parameters), not static instances - **Use `Base44Command`** - All commands use `new Base44Command(name, options?)` - **No context plumbing** - Context is injected automatically. Command files should never import `CLIContext`. +- **Use `command.logger` for output** - Never import `log` from `@clack/prompts` in command files. Use `command.logger` (or pass the logger to helper functions) so output works correctly in both interactive and non-interactive modes. - **Task wrapper** - Use `runTask()` for async operations with spinners - **Use theme for styling** - Never use `chalk` directly; import `theme` from `@/cli/utils/` and use semantic names - **Use fs.ts utilities** - Always use `@/core/utils/fs.js` for file operations diff --git a/packages/cli/src/cli/commands/agents/pull.ts b/packages/cli/src/cli/commands/agents/pull.ts index 6b5b262c..409a7987 100644 --- a/packages/cli/src/cli/commands/agents/pull.ts +++ b/packages/cli/src/cli/commands/agents/pull.ts @@ -1,12 +1,12 @@ import { dirname, join } from "node:path"; -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { readProjectConfig } from "@/core/index.js"; import { fetchAgents, writeAgents } from "@/core/resources/agent/index.js"; -async function pullAgentsAction(): Promise { +async function pullAgentsAction(logger: Logger): Promise { const { project } = await readProjectConfig(); const configDir = dirname(project.configPath); @@ -35,13 +35,13 @@ async function pullAgentsAction(): Promise { ); if (written.length > 0) { - log.success(`Written: ${written.join(", ")}`); + logger.success(`Written: ${written.join(", ")}`); } if (deleted.length > 0) { - log.warn(`Deleted: ${deleted.join(", ")}`); + logger.warn(`Deleted: ${deleted.join(", ")}`); } if (written.length === 0 && deleted.length === 0) { - log.info("All agents are already up to date"); + logger.info("All agents are already up to date"); } return { @@ -54,5 +54,7 @@ export function getAgentsPullCommand(): Command { .description( "Pull agents from Base44 to local files (replaces all local agent configs)", ) - .action(pullAgentsAction); + .action((_options: unknown, command: Base44Command) => + pullAgentsAction(command.logger), + ); } diff --git a/packages/cli/src/cli/commands/agents/push.ts b/packages/cli/src/cli/commands/agents/push.ts index 35a055b1..ae51d2cb 100644 --- a/packages/cli/src/cli/commands/agents/push.ts +++ b/packages/cli/src/cli/commands/agents/push.ts @@ -1,14 +1,14 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { readProjectConfig } from "@/core/index.js"; import { pushAgents } from "@/core/resources/agent/index.js"; -async function pushAgentsAction(): Promise { +async function pushAgentsAction(logger: Logger): Promise { const { agents } = await readProjectConfig(); - log.info( + logger.info( agents.length === 0 ? "No local agents found - this will delete all remote agents" : `Found ${agents.length} agents to push`, @@ -26,13 +26,13 @@ async function pushAgentsAction(): Promise { ); if (result.created.length > 0) { - log.success(`Created: ${result.created.join(", ")}`); + logger.success(`Created: ${result.created.join(", ")}`); } if (result.updated.length > 0) { - log.success(`Updated: ${result.updated.join(", ")}`); + logger.success(`Updated: ${result.updated.join(", ")}`); } if (result.deleted.length > 0) { - log.warn(`Deleted: ${result.deleted.join(", ")}`); + logger.warn(`Deleted: ${result.deleted.join(", ")}`); } return { outroMessage: "Agents pushed to Base44" }; @@ -43,5 +43,7 @@ export function getAgentsPushCommand(): Command { .description( "Push local agents to Base44 (replaces all remote agent configs)", ) - .action(pushAgentsAction); + .action((_options: unknown, command: Base44Command) => + pushAgentsAction(command.logger), + ); } diff --git a/packages/cli/src/cli/commands/auth/login-flow.ts b/packages/cli/src/cli/commands/auth/login-flow.ts index 5cb62f15..f3df8d2a 100644 --- a/packages/cli/src/cli/commands/auth/login-flow.ts +++ b/packages/cli/src/cli/commands/auth/login-flow.ts @@ -1,7 +1,7 @@ -import { log } from "@clack/prompts"; import pWaitFor from "p-wait-for"; import type { RunCommandResult } from "@/cli/types.js"; import { runTask } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { theme } from "@/cli/utils/theme.js"; import type { DeviceCodeResponse, @@ -15,7 +15,9 @@ import { writeAuth, } from "@/core/auth/index.js"; -async function generateAndDisplayDeviceCode(): Promise { +async function generateAndDisplayDeviceCode( + logger: Logger, +): Promise { const deviceCodeResponse = await runTask( "Generating device code...", async () => { @@ -27,7 +29,7 @@ async function generateAndDisplayDeviceCode(): Promise { }, ); - log.info( + logger.info( `Verification code: ${theme.styles.bold(deviceCodeResponse.userCode)}` + `\nPlease confirm this code at: ${deviceCodeResponse.verificationUri}`, ); @@ -99,8 +101,8 @@ async function saveAuthData( * Execute the login flow (device code authentication). * This function is separate from the command to avoid circular dependencies. */ -export async function login(): Promise { - const deviceCodeResponse = await generateAndDisplayDeviceCode(); +export async function login(logger: Logger): Promise { + const deviceCodeResponse = await generateAndDisplayDeviceCode(logger); const token = await waitForAuthentication( deviceCodeResponse.deviceCode, diff --git a/packages/cli/src/cli/commands/auth/login.ts b/packages/cli/src/cli/commands/auth/login.ts index 860921ab..a4516871 100644 --- a/packages/cli/src/cli/commands/auth/login.ts +++ b/packages/cli/src/cli/commands/auth/login.ts @@ -8,5 +8,7 @@ export function getLoginCommand(): Command { requireAppConfig: false, }) .description("Authenticate with Base44") - .action(login); + .action((_options: unknown, command: Base44Command) => + login(command.logger), + ); } diff --git a/packages/cli/src/cli/commands/connectors/list-available.ts b/packages/cli/src/cli/commands/connectors/list-available.ts index 9d940f15..e40d2b4b 100644 --- a/packages/cli/src/cli/commands/connectors/list-available.ts +++ b/packages/cli/src/cli/commands/connectors/list-available.ts @@ -1,4 +1,3 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { @@ -7,9 +6,10 @@ import { runTask, YAML_INDENT, } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { listAvailableIntegrations } from "@/core/resources/connector/index.js"; -async function listAvailableAction(): Promise { +async function listAvailableAction(logger: Logger): Promise { const { integrations } = await runTask( "Fetching available integrations from Base44", async () => { @@ -28,7 +28,7 @@ async function listAvailableAction(): Promise { for (const { displayName, ...rest } of integrations) { const yaml = formatYaml(rest); const pad = " ".repeat(YAML_INDENT); - log.info(`${displayName}\n${pad}${yaml.replace(/\n/g, `\n${pad}`)}`); + logger.info(`${displayName}\n${pad}${yaml.replace(/\n/g, `\n${pad}`)}`); } return { @@ -39,5 +39,7 @@ async function listAvailableAction(): Promise { export function getConnectorsListAvailableCommand(): Command { return new Base44Command("list-available") .description("List all available integration types") - .action(listAvailableAction); + .action((_options: unknown, command: Base44Command) => + listAvailableAction(command.logger), + ); } diff --git a/packages/cli/src/cli/commands/connectors/oauth-prompt.ts b/packages/cli/src/cli/commands/connectors/oauth-prompt.ts index df83a0c5..a787ff4d 100644 --- a/packages/cli/src/cli/commands/connectors/oauth-prompt.ts +++ b/packages/cli/src/cli/commands/connectors/oauth-prompt.ts @@ -1,7 +1,8 @@ -import { confirm, isCancel, log, spinner } from "@clack/prompts"; +import { confirm, isCancel, spinner } from "@clack/prompts"; import open from "open"; import pWaitFor, { TimeoutError } from "p-wait-for"; import { theme } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import type { ConnectorOAuthStatus, ConnectorSyncResult, @@ -103,6 +104,7 @@ async function runOAuthFlowWithSkip( */ export async function promptOAuthFlows( pending: OAuthSyncResult[], + logger: Logger, options?: OAuthPromptOptions, ): Promise> { const outcomes = new Map(); @@ -111,11 +113,13 @@ export async function promptOAuthFlows( return outcomes; } - log.warn( + logger.warn( `${pending.length} connector(s) require authorization in your browser:`, ); for (const connector of pending) { - log.info(` ${connector.type}: ${theme.styles.dim(connector.redirectUrl)}`); + logger.info( + ` ${connector.type}: ${theme.styles.dim(connector.redirectUrl)}`, + ); } if (options?.skipPrompt) { @@ -132,11 +136,11 @@ export async function promptOAuthFlows( for (const connector of pending) { try { - log.info(`Opening browser for ${connector.type}...`); + logger.info(`Opening browser for ${connector.type}...`); const status = await runOAuthFlowWithSkip(connector); outcomes.set(connector.type, status); } catch (err) { - log.error( + logger.error( `Failed to authorize ${connector.type}: ${err instanceof Error ? err.message : String(err)}`, ); outcomes.set(connector.type, "FAILED"); diff --git a/packages/cli/src/cli/commands/connectors/pull.ts b/packages/cli/src/cli/commands/connectors/pull.ts index b4ff3152..0a4dda69 100644 --- a/packages/cli/src/cli/commands/connectors/pull.ts +++ b/packages/cli/src/cli/commands/connectors/pull.ts @@ -1,15 +1,15 @@ import { dirname, join } from "node:path"; -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { readProjectConfig } from "@/core/index.js"; import { pullAllConnectors, writeConnectors, } from "@/core/resources/connector/index.js"; -async function pullConnectorsAction(): Promise { +async function pullConnectorsAction(logger: Logger): Promise { const { project } = await readProjectConfig(); const configDir = dirname(project.configPath); @@ -38,13 +38,13 @@ async function pullConnectorsAction(): Promise { ); if (written.length > 0) { - log.success(`Written: ${written.join(", ")}`); + logger.success(`Written: ${written.join(", ")}`); } if (deleted.length > 0) { - log.warn(`Deleted: ${deleted.join(", ")}`); + logger.warn(`Deleted: ${deleted.join(", ")}`); } if (written.length === 0 && deleted.length === 0) { - log.info("All connectors are already up to date"); + logger.info("All connectors are already up to date"); } return { @@ -57,5 +57,7 @@ export function getConnectorsPullCommand(): Command { .description( "Pull connectors from Base44 to local files (replaces all local connector configs)", ) - .action(pullConnectorsAction); + .action((_options: unknown, command: Base44Command) => + pullConnectorsAction(command.logger), + ); } diff --git a/packages/cli/src/cli/commands/connectors/push.ts b/packages/cli/src/cli/commands/connectors/push.ts index 13048b89..4ac6830d 100644 --- a/packages/cli/src/cli/commands/connectors/push.ts +++ b/packages/cli/src/cli/commands/connectors/push.ts @@ -1,7 +1,7 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask, theme } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { getConnectorsUrl } from "@/cli/utils/urls.js"; import { readProjectConfig } from "@/core/index.js"; import { @@ -19,6 +19,7 @@ import { function printSummary( results: ConnectorSyncResult[], oauthOutcomes: Map, + logger: Logger, ): void { const synced: IntegrationType[] = []; const added: IntegrationType[] = []; @@ -59,48 +60,49 @@ function printSummary( } } - log.info(theme.styles.bold("Summary:")); + logger.info(theme.styles.bold("Summary:")); if (provisioned) { - log.success("Stripe sandbox provisioned"); + logger.success("Stripe sandbox provisioned"); if (provisioned.claimUrl) { - log.info( + logger.info( ` Claim your Stripe sandbox: ${theme.colors.links(provisioned.claimUrl)}`, ); } - log.info( + logger.info( ` Connectors dashboard: ${theme.colors.links(getConnectorsUrl())}`, ); } if (synced.length > 0) { - log.success(`Synced: ${synced.join(", ")}`); + logger.success(`Synced: ${synced.join(", ")}`); } if (added.length > 0) { - log.success(`Added: ${added.join(", ")}`); + logger.success(`Added: ${added.join(", ")}`); } if (removed.length > 0) { - log.info(theme.styles.dim(`Removed: ${removed.join(", ")}`)); + logger.info(theme.styles.dim(`Removed: ${removed.join(", ")}`)); } if (skipped.length > 0) { - log.warn(`Skipped: ${skipped.join(", ")}`); + logger.warn(`Skipped: ${skipped.join(", ")}`); } for (const r of failed) { - log.error(`Failed: ${r.type} - ${r.error}`); + logger.error(`Failed: ${r.type} - ${r.error}`); } } async function pushConnectorsAction( isNonInteractive: boolean, + logger: Logger, ): Promise { const { connectors } = await readProjectConfig(); if (connectors.length === 0) { - log.info( + logger.info( "No local connectors found - checking for remote connectors to remove", ); } else { const connectorNames = connectors.map((c) => c.type).join(", "); - log.info( + logger.info( `Found ${connectors.length} connectors to push: ${connectorNames}`, ); } @@ -115,7 +117,7 @@ async function pushConnectorsAction( const needsOAuth = filterPendingOAuth(results); let outroMessage = "Connectors pushed to Base44"; - const oauthOutcomes = await promptOAuthFlows(needsOAuth, { + const oauthOutcomes = await promptOAuthFlows(needsOAuth, logger, { skipPrompt: isNonInteractive, }); @@ -128,7 +130,7 @@ async function pushConnectorsAction( : "Some connectors still require authorization. Run 'base44 connectors push' or open the links above to authorize."; } - printSummary(results, oauthOutcomes); + printSummary(results, oauthOutcomes, logger); return { outroMessage }; } @@ -138,6 +140,9 @@ export function getConnectorsPushCommand(): Command { "Push local connectors to Base44 (overwrites connectors on Base44)", ) .action(async (_options: unknown, command: Base44Command) => { - return await pushConnectorsAction(command.isNonInteractive); + return await pushConnectorsAction( + command.isNonInteractive, + command.logger, + ); }); } diff --git a/packages/cli/src/cli/commands/entities/push.ts b/packages/cli/src/cli/commands/entities/push.ts index ccc0eed8..3c1de944 100644 --- a/packages/cli/src/cli/commands/entities/push.ts +++ b/packages/cli/src/cli/commands/entities/push.ts @@ -1,11 +1,11 @@ -import { log } from "@clack/prompts"; import { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { readProjectConfig } from "@/core/index.js"; import { pushEntities } from "@/core/resources/entity/index.js"; -async function pushEntitiesAction(): Promise { +async function pushEntitiesAction(logger: Logger): Promise { const { entities } = await readProjectConfig(); if (entities.length === 0) { @@ -13,7 +13,7 @@ async function pushEntitiesAction(): Promise { } const entityNames = entities.map((e) => e.name).join(", "); - log.info(`Found ${entities.length} entities to push: ${entityNames}`); + logger.info(`Found ${entities.length} entities to push: ${entityNames}`); const result = await runTask( "Pushing entities to Base44", @@ -28,13 +28,13 @@ async function pushEntitiesAction(): Promise { // Print the results if (result.created.length > 0) { - log.success(`Created: ${result.created.join(", ")}`); + logger.success(`Created: ${result.created.join(", ")}`); } if (result.updated.length > 0) { - log.success(`Updated: ${result.updated.join(", ")}`); + logger.success(`Updated: ${result.updated.join(", ")}`); } if (result.deleted.length > 0) { - log.warn(`Deleted: ${result.deleted.join(", ")}`); + logger.warn(`Deleted: ${result.deleted.join(", ")}`); } return { outroMessage: "Entities pushed to Base44" }; @@ -46,6 +46,8 @@ export function getEntitiesPushCommand(): Command { .addCommand( new Base44Command("push") .description("Push local entities to Base44") - .action(pushEntitiesAction), + .action((_options: unknown, command: Base44Command) => + pushEntitiesAction(command.logger), + ), ); } diff --git a/packages/cli/src/cli/commands/functions/deploy.ts b/packages/cli/src/cli/commands/functions/deploy.ts index 1fd32924..a91a1b27 100644 --- a/packages/cli/src/cli/commands/functions/deploy.ts +++ b/packages/cli/src/cli/commands/functions/deploy.ts @@ -1,9 +1,9 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; import { formatDeployResult } from "@/cli/commands/functions/formatDeployResult.js"; import { parseNames } from "@/cli/commands/functions/parseNames.js"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, theme } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { InvalidInputError } from "@/core/errors.js"; import { readProjectConfig } from "@/core/index.js"; import { @@ -29,18 +29,18 @@ function resolveFunctionsToDeploy( return allFunctions.filter((f) => names.includes(f.name)); } -function formatPruneResult(pruneResult: PruneResult): void { +function formatPruneResult(pruneResult: PruneResult, logger: Logger): void { if (pruneResult.deleted) { - log.success(`${pruneResult.name.padEnd(25)} deleted`); + logger.success(`${pruneResult.name.padEnd(25)} deleted`); } else { - log.error(`${pruneResult.name.padEnd(25)} error: ${pruneResult.error}`); + logger.error(`${pruneResult.name.padEnd(25)} error: ${pruneResult.error}`); } } -function formatPruneSummary(pruneResults: PruneResult[]): void { +function formatPruneSummary(pruneResults: PruneResult[], logger: Logger): void { if (pruneResults.length > 0) { const pruned = pruneResults.filter((r) => r.deleted).length; - log.info(`${pruned} deleted`); + logger.info(`${pruned} deleted`); } } @@ -59,6 +59,7 @@ function buildDeploySummary(results: SingleFunctionDeployResult[]): string { async function deployFunctionsAction( names: string[], options: { force?: boolean }, + logger: Logger, ): Promise { if (options.force && names.length > 0) { throw new InvalidInputError( @@ -76,7 +77,7 @@ async function deployFunctionsAction( }; } - log.info( + logger.info( `Found ${toDeploy.length} ${toDeploy.length === 1 ? "function" : "functions"} to deploy`, ); @@ -89,13 +90,13 @@ async function deployFunctionsAction( startNames.length === 1 ? startNames[0] : `${startNames.length} functions`; - log.step( + logger.step( theme.styles.dim(`[${completed + 1}/${total}] Deploying ${label}...`), ); }, onResult: (result) => { completed++; - formatDeployResult(result); + formatDeployResult(result, logger); }, }); @@ -107,22 +108,22 @@ async function deployFunctionsAction( onStart: (total) => { pruneTotal = total; if (total > 0) { - log.info( + logger.info( `Found ${total} remote ${total === 1 ? "function" : "functions"} to delete`, ); } }, onBeforeDelete: (name) => { pruneCompleted++; - log.step( + logger.step( theme.styles.dim( `[${pruneCompleted}/${pruneTotal}] Deleting ${name}...`, ), ); }, - onResult: formatPruneResult, + onResult: (r) => formatPruneResult(r, logger), }); - formatPruneSummary(pruneResults); + formatPruneSummary(pruneResults, logger); } return { outroMessage: buildDeploySummary(results) }; @@ -133,8 +134,14 @@ export function getDeployCommand(): Command { .description("Deploy functions to Base44") .argument("[names...]", "Function names to deploy (deploys all if omitted)") .option("--force", "Delete remote functions not found locally") - .action(async (rawNames: string[], options: { force?: boolean }) => { - const names = parseNames(rawNames); - return deployFunctionsAction(names, options); - }); + .action( + async ( + rawNames: string[], + options: { force?: boolean }, + command: Base44Command, + ) => { + const names = parseNames(rawNames); + return deployFunctionsAction(names, options, command.logger); + }, + ); } diff --git a/packages/cli/src/cli/commands/functions/formatDeployResult.ts b/packages/cli/src/cli/commands/functions/formatDeployResult.ts index 5a4be23f..3e0dad42 100644 --- a/packages/cli/src/cli/commands/functions/formatDeployResult.ts +++ b/packages/cli/src/cli/commands/functions/formatDeployResult.ts @@ -1,4 +1,4 @@ -import { log } from "@clack/prompts"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { theme } from "@/cli/utils/theme.js"; import type { SingleFunctionDeployResult } from "@/core/resources/function/deploy.js"; @@ -6,16 +6,19 @@ function formatDuration(ms: number): string { return `${(ms / 1000).toFixed(1)}s`; } -export function formatDeployResult(result: SingleFunctionDeployResult): void { +export function formatDeployResult( + result: SingleFunctionDeployResult, + logger: Logger, +): void { const label = result.name.padEnd(25); if (result.status === "deployed") { const timing = result.durationMs ? theme.styles.dim(` (${formatDuration(result.durationMs)})`) : ""; - log.success(`${label} deployed${timing}`); + logger.success(`${label} deployed${timing}`); } else if (result.status === "unchanged") { - log.success(`${label} unchanged`); + logger.success(`${label} unchanged`); } else { - log.error(`${label} error: ${result.error}`); + logger.error(`${label} error: ${result.error}`); } } diff --git a/packages/cli/src/cli/commands/functions/list.ts b/packages/cli/src/cli/commands/functions/list.ts index 81c453eb..ce4ea1a8 100644 --- a/packages/cli/src/cli/commands/functions/list.ts +++ b/packages/cli/src/cli/commands/functions/list.ts @@ -1,10 +1,10 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask, theme } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { listDeployedFunctions } from "@/core/resources/function/api.js"; -async function listFunctionsAction(): Promise { +async function listFunctionsAction(logger: Logger): Promise { const { functions } = await runTask( "Fetching functions...", async () => listDeployedFunctions(), @@ -23,7 +23,7 @@ async function listFunctionsAction(): Promise { ` (${automationCount} automation${automationCount > 1 ? "s" : ""})`, ) : ""; - log.message(` ${fn.name}${automationLabel}`); + logger.message(` ${fn.name}${automationLabel}`); } return { @@ -34,5 +34,7 @@ async function listFunctionsAction(): Promise { export function getListCommand(): Command { return new Base44Command("list") .description("List all deployed functions") - .action(listFunctionsAction); + .action((_options: unknown, command: Base44Command) => + listFunctionsAction(command.logger), + ); } diff --git a/packages/cli/src/cli/commands/functions/pull.ts b/packages/cli/src/cli/commands/functions/pull.ts index c4bcda59..9ad3edfc 100644 --- a/packages/cli/src/cli/commands/functions/pull.ts +++ b/packages/cli/src/cli/commands/functions/pull.ts @@ -1,14 +1,15 @@ import { dirname, join } from "node:path"; -import { log } from "@clack/prompts"; import type { Command } from "commander"; import type { RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { readProjectConfig } from "@/core/index.js"; import { listDeployedFunctions } from "@/core/resources/function/api.js"; import { writeFunctions } from "@/core/resources/function/pull.js"; async function pullFunctionsAction( name: string | undefined, + logger: Logger, ): Promise { const { project } = await readProjectConfig(); @@ -53,10 +54,10 @@ async function pullFunctionsAction( ); for (const name of written) { - log.success(`${name.padEnd(25)} written`); + logger.success(`${name.padEnd(25)} written`); } for (const name of skipped) { - log.info(`${name.padEnd(25)} unchanged`); + logger.info(`${name.padEnd(25)} unchanged`); } return { @@ -68,5 +69,13 @@ export function getPullCommand(): Command { return new Base44Command("pull") .description("Pull deployed functions from Base44") .argument("[name]", "Function name to pull (pulls all if omitted)") - .action(pullFunctionsAction); + .action( + async ( + name: string | undefined, + _options: unknown, + command: Base44Command, + ) => { + return pullFunctionsAction(name, command.logger); + }, + ); } diff --git a/packages/cli/src/cli/commands/project/create.ts b/packages/cli/src/cli/commands/project/create.ts index b26ef101..07f3915a 100644 --- a/packages/cli/src/cli/commands/project/create.ts +++ b/packages/cli/src/cli/commands/project/create.ts @@ -1,6 +1,6 @@ import { basename, join, resolve } from "node:path"; import type { Option } from "@clack/prompts"; -import { confirm, group, isCancel, log, select, text } from "@clack/prompts"; +import { confirm, group, isCancel, select, text } from "@clack/prompts"; import { Argument, type Command } from "commander"; import { execa } from "execa"; import kebabCase from "lodash/kebabCase"; @@ -12,6 +12,7 @@ import { runTask, theme, } from "@/cli/utils/index.js"; +import type { Logger } from "@/cli/utils/logger/types.js"; import { InvalidInputError } from "@/core/errors.js"; import { deploySite, isDirEmpty, pushEntities } from "@/core/index.js"; import type { Template } from "@/core/project/index.js"; @@ -56,6 +57,7 @@ function validateNonInteractiveFlags(command: Command): void { async function createInteractive( options: CreateOptions, + logger: Logger, ): Promise { const templates = await listTemplates(); const templateOptions: Option