diff --git a/bun.lock b/bun.lock index 54fd41dd..87dfbb8b 100644 --- a/bun.lock +++ b/bun.lock @@ -15,11 +15,12 @@ }, "packages/cli": { "name": "base44", - "version": "0.0.45", + "version": "0.0.47", "bin": { "base44": "./bin/run.js", }, "devDependencies": { + "@base44-cli/logger": "workspace:*", "@clack/prompts": "^1.0.1", "@seald-io/nedb": "^4.1.2", "@types/bun": "^1.2.15", @@ -65,10 +66,20 @@ "zod": "^4.3.5", }, }, + "packages/logger": { + "name": "@base44-cli/logger", + "version": "0.0.1", + "devDependencies": { + "@clack/prompts": "^1.0.1", + "typescript": "^5.7.2", + }, + }, }, "packages": { "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.9.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="], + "@base44-cli/logger": ["@base44-cli/logger@workspace:packages/logger"], + "@biomejs/biome": ["@biomejs/biome@2.4.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.6", "@biomejs/cli-darwin-x64": "2.4.6", "@biomejs/cli-linux-arm64": "2.4.6", "@biomejs/cli-linux-arm64-musl": "2.4.6", "@biomejs/cli-linux-x64": "2.4.6", "@biomejs/cli-linux-x64-musl": "2.4.6", "@biomejs/cli-win32-arm64": "2.4.6", "@biomejs/cli-win32-x64": "2.4.6" }, "bin": { "biome": "bin/biome" } }, "sha512-QnHe81PMslpy3mnpL8DnO2M4S4ZnYPkjlGCLWBZT/3R9M6b5daArWMMtEfP52/n174RKnwRIf3oT8+wc9ihSfQ=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NW18GSyxr+8sJIqgoGwVp5Zqm4SALH4b4gftIA0n62PTuBs6G2tHlwNAOj0Vq0KKSs7Sf88VjjmHh0O36EnzrQ=="], diff --git a/docs/commands.md b/docs/commands.md index 42b55203..5504f158 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, CLIContext, 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,11 @@ 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 type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask, theme } from "@/cli/utils/index.js"; -async function myAction(): Promise { +async function myAction({ logger }: CLIContext): Promise { const result = await runTask( "Doing something...", async () => { @@ -26,7 +25,7 @@ async function myAction(): Promise { } ); - log.success("Operation completed!"); + logger.success("Operation completed!"); return { outroMessage: `Created ${theme.styles.bold(result.name)}` }; } @@ -44,6 +43,9 @@ export function getMyCommand(): Command { - Use `Base44Command` class - Commands must NOT call `intro()` or `outro()` directly - The action function must return `RunCommandResult` with an `outroMessage` +- Action functions receive `CLIContext` as their first argument (injected by `Base44Command`), followed by Commander's positional args and options +- Destructure what you need from `CLIContext`: `{ logger }`, `{ logger, isNonInteractive }`, or `_ctx` if nothing needed +- Use `.action(fn)` directly — no wrapper needed ## Base44Command Options @@ -78,63 +80,70 @@ program.addCommand(getMyCommand()); ## CLIContext (Automatic Injection) +`CLIContext` is automatically injected as the **first argument** to all action functions by `Base44Command`. Destructure what you need: + ```typescript 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. ### Using `isNonInteractive` -When a command needs to check `isNonInteractive` (e.g., to skip browser opens or confirmation prompts), access it from the command instance. Commander passes the command as the last argument to action handlers: +Destructure `isNonInteractive` from the context first argument: ```typescript -export function getMyCommand(): Command { - return new Base44Command("open") - .description("Open something in browser") - .action(async (_options: unknown, command: Base44Command) => { - return await myAction(command.isNonInteractive); - }); -} - -async function myAction(isNonInteractive: boolean): Promise { +async function openDashboard({ isNonInteractive }: CLIContext): Promise { if (!isNonInteractive) { await open(url); // Only open browser in interactive mode } return { outroMessage: `Opened at ${url}` }; } + +export function getMyCommand(): Command { + return new Base44Command("open") + .description("Open something in browser") + .action(openDashboard); +} ``` ### Guarding Interactive Commands -Commands that use interactive prompts (`select`, `text`, `confirm`, `group` from `@clack/prompts`) **must** guard against non-interactive environments. If the user didn't provide enough flags to skip all prompts, error early: +Commands that use interactive prompts (`select`, `text`, `confirm`, `group` from `@clack/prompts`) **must** guard against non-interactive environments. Move the guard into the action function: ```typescript +async function myAction( + { isNonInteractive }: CLIContext, + options: MyOptions, +): Promise { + const skipPrompts = !!options.name && !!options.path; + if (!skipPrompts && isNonInteractive) { + throw new InvalidInputError( + "--name and --path are required in non-interactive mode", + ); + } + if (skipPrompts) { + return await createNonInteractive(options); + } + return await createInteractive(options); +} + export function getMyCommand(): Command { return new Base44Command("my-cmd", { requireAppConfig: false }) .option("-n, --name ", "Project name") .option("-p, --path ", "Project path") - .action(async (options: MyOptions, command: Base44Command) => { - const skipPrompts = !!options.name && !!options.path; - if (!skipPrompts && command.isNonInteractive) { - throw new InvalidInputError( - "--name and --path are required in non-interactive mode", - ); - } - if (skipPrompts) { - return await createNonInteractive(options); - } - return await createInteractive(options); - }); + .action(myAction); } ``` -Commands that only use `log.*` (display-only, no input) don't need this guard. See `project/create.ts`, `project/link.ts`, and `project/eject.ts` for real examples. +Commands that only use `logger.*` (display-only, no input) don't need this guard. See `project/create.ts`, `project/link.ts`, and `project/eject.ts` for real examples. ## runTask (Async Operations with Spinners) @@ -153,7 +162,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 @@ -211,9 +220,7 @@ export function getMyCommand(): Command { .argument("[entries...]", "Input entries") .option("--flag-a ", "Alternative input") .hook("preAction", validateInput) - .action(async (entries, options) => { - return await myAction(entries, options); - }); + .action(myAction); } ``` @@ -223,9 +230,10 @@ 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`. +- **CLIContext as first arg** - All action functions receive `CLIContext` as their first argument (auto-injected). Destructure `{ logger }`, `{ logger, isNonInteractive }`, or use `_ctx` if nothing is needed. Use `.action(fn)` directly — no wrappers. +- **Use `logger` for output** - Never import `log` from `@clack/prompts` in command files. Use the `logger` from CLIContext so output works correctly in both interactive and non-interactive modes. Pass `logger` to helper functions as a parameter. - **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 -- **Guard interactive prompts** - Commands using `select`, `text`, `confirm`, or `group` from `@clack/prompts` must check `command.isNonInteractive` and throw `InvalidInputError` if required flags are missing. Never let prompts hang in CI. +- **Guard interactive prompts** - Commands using `select`, `text`, `confirm`, or `group` from `@clack/prompts` must check `isNonInteractive` (from CLIContext) and throw `InvalidInputError` if required flags are missing. Never let prompts hang in CI. - **Consistent copy across related commands** - User-facing messages (errors, success, hints) for commands in the same group should use consistent language and structure. When writing validation errors, outro messages, or spinner text, check sibling commands for parity so the product voice stays coherent. diff --git a/knip.json b/knip.json index 46847285..ae1c72be 100644 --- a/knip.json +++ b/knip.json @@ -7,6 +7,9 @@ "project": ["src/**/*.ts", "tests/**/*.ts"], "ignore": ["dist/**", "tests/fixtures/**"], "ignoreDependencies": ["@types/deno"] + }, + "packages/logger": { + "project": ["src/**/*.ts"] } } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 0b09b236..67fc7b81 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,6 +39,7 @@ "url": "https://github.com/base44/cli" }, "devDependencies": { + "@base44-cli/logger": "workspace:*", "@clack/prompts": "^1.0.1", "@seald-io/nedb": "^4.1.2", "@types/bun": "^1.2.15", diff --git a/packages/cli/src/cli/commands/agents/pull.ts b/packages/cli/src/cli/commands/agents/pull.ts index 6b5b262c..d61ffc87 100644 --- a/packages/cli/src/cli/commands/agents/pull.ts +++ b/packages/cli/src/cli/commands/agents/pull.ts @@ -1,12 +1,13 @@ import { dirname, join } from "node:path"; -import { log } from "@clack/prompts"; import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; import { readProjectConfig } from "@/core/index.js"; import { fetchAgents, writeAgents } from "@/core/resources/agent/index.js"; -async function pullAgentsAction(): Promise { +async function pullAgentsAction({ + log, +}: CLIContext): Promise { const { project } = await readProjectConfig(); const configDir = dirname(project.configPath); diff --git a/packages/cli/src/cli/commands/agents/push.ts b/packages/cli/src/cli/commands/agents/push.ts index 35a055b1..fa0361b9 100644 --- a/packages/cli/src/cli/commands/agents/push.ts +++ b/packages/cli/src/cli/commands/agents/push.ts @@ -1,11 +1,12 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; import { readProjectConfig } from "@/core/index.js"; import { pushAgents } from "@/core/resources/agent/index.js"; -async function pushAgentsAction(): Promise { +async function pushAgentsAction({ + log, +}: CLIContext): Promise { const { agents } = await readProjectConfig(); log.info( diff --git a/packages/cli/src/cli/commands/auth/login-flow.ts b/packages/cli/src/cli/commands/auth/login-flow.ts index 5cb62f15..946b13a2 100644 --- a/packages/cli/src/cli/commands/auth/login-flow.ts +++ b/packages/cli/src/cli/commands/auth/login-flow.ts @@ -1,6 +1,6 @@ -import { log } from "@clack/prompts"; +import type { Logger } from "@base44-cli/logger"; import pWaitFor from "p-wait-for"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { runTask } from "@/cli/utils/index.js"; import { theme } from "@/cli/utils/theme.js"; import type { @@ -15,7 +15,9 @@ import { writeAuth, } from "@/core/auth/index.js"; -async function generateAndDisplayDeviceCode(): Promise { +async function generateAndDisplayDeviceCode( + log: Logger, +): Promise { const deviceCodeResponse = await runTask( "Generating device code...", async () => { @@ -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({ log }: CLIContext): Promise { + const deviceCodeResponse = await generateAndDisplayDeviceCode(log); const token = await waitForAuthentication( deviceCodeResponse.deviceCode, diff --git a/packages/cli/src/cli/commands/auth/logout.ts b/packages/cli/src/cli/commands/auth/logout.ts index 2fd58a0e..dd51ee41 100644 --- a/packages/cli/src/cli/commands/auth/logout.ts +++ b/packages/cli/src/cli/commands/auth/logout.ts @@ -1,9 +1,9 @@ import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command } from "@/cli/utils/index.js"; import { deleteAuth } from "@/core/auth/index.js"; -async function logout(): Promise { +async function logout(_ctx: CLIContext): Promise { await deleteAuth(); return { outroMessage: "Logged out successfully" }; } diff --git a/packages/cli/src/cli/commands/auth/whoami.ts b/packages/cli/src/cli/commands/auth/whoami.ts index 26a6e01f..8f84d272 100644 --- a/packages/cli/src/cli/commands/auth/whoami.ts +++ b/packages/cli/src/cli/commands/auth/whoami.ts @@ -1,9 +1,9 @@ import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, theme } from "@/cli/utils/index.js"; import { readAuth } from "@/core/auth/index.js"; -async function whoami(): Promise { +async function whoami(_ctx: CLIContext): Promise { const auth = await readAuth(); return { outroMessage: `Logged in as: ${theme.styles.bold(auth.email)}` }; } diff --git a/packages/cli/src/cli/commands/connectors/list-available.ts b/packages/cli/src/cli/commands/connectors/list-available.ts index 9d940f15..9ca8af94 100644 --- a/packages/cli/src/cli/commands/connectors/list-available.ts +++ b/packages/cli/src/cli/commands/connectors/list-available.ts @@ -1,6 +1,5 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, formatYaml, @@ -9,7 +8,9 @@ import { } from "@/cli/utils/index.js"; import { listAvailableIntegrations } from "@/core/resources/connector/index.js"; -async function listAvailableAction(): Promise { +async function listAvailableAction({ + log, +}: CLIContext): Promise { const { integrations } = await runTask( "Fetching available integrations from Base44", async () => { diff --git a/packages/cli/src/cli/commands/connectors/oauth-prompt.ts b/packages/cli/src/cli/commands/connectors/oauth-prompt.ts index df83a0c5..a8b19fa7 100644 --- a/packages/cli/src/cli/commands/connectors/oauth-prompt.ts +++ b/packages/cli/src/cli/commands/connectors/oauth-prompt.ts @@ -1,4 +1,5 @@ -import { confirm, isCancel, log, spinner } from "@clack/prompts"; +import type { Logger } from "@base44-cli/logger"; +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"; @@ -103,6 +104,7 @@ async function runOAuthFlowWithSkip( */ export async function promptOAuthFlows( pending: OAuthSyncResult[], + log: Logger, options?: OAuthPromptOptions, ): Promise> { const outcomes = new Map(); diff --git a/packages/cli/src/cli/commands/connectors/pull.ts b/packages/cli/src/cli/commands/connectors/pull.ts index b4ff3152..6f3ef220 100644 --- a/packages/cli/src/cli/commands/connectors/pull.ts +++ b/packages/cli/src/cli/commands/connectors/pull.ts @@ -1,7 +1,6 @@ import { dirname, join } from "node:path"; -import { log } from "@clack/prompts"; import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; import { readProjectConfig } from "@/core/index.js"; import { @@ -9,7 +8,9 @@ import { writeConnectors, } from "@/core/resources/connector/index.js"; -async function pullConnectorsAction(): Promise { +async function pullConnectorsAction({ + log, +}: CLIContext): Promise { const { project } = await readProjectConfig(); const configDir = dirname(project.configPath); diff --git a/packages/cli/src/cli/commands/connectors/push.ts b/packages/cli/src/cli/commands/connectors/push.ts index 13048b89..73be9cff 100644 --- a/packages/cli/src/cli/commands/connectors/push.ts +++ b/packages/cli/src/cli/commands/connectors/push.ts @@ -1,6 +1,6 @@ -import { log } from "@clack/prompts"; +import type { Logger } from "@base44-cli/logger"; import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask, theme } from "@/cli/utils/index.js"; import { getConnectorsUrl } from "@/cli/utils/urls.js"; import { readProjectConfig } from "@/core/index.js"; @@ -19,6 +19,7 @@ import { function printSummary( results: ConnectorSyncResult[], oauthOutcomes: Map, + log: Logger, ): void { const synced: IntegrationType[] = []; const added: IntegrationType[] = []; @@ -89,9 +90,10 @@ function printSummary( } } -async function pushConnectorsAction( - isNonInteractive: boolean, -): Promise { +async function pushConnectorsAction({ + isNonInteractive, + log, +}: CLIContext): Promise { const { connectors } = await readProjectConfig(); if (connectors.length === 0) { @@ -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, log, { 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, log); return { outroMessage }; } @@ -137,7 +139,5 @@ export function getConnectorsPushCommand(): Command { .description( "Push local connectors to Base44 (overwrites connectors on Base44)", ) - .action(async (_options: unknown, command: Base44Command) => { - return await pushConnectorsAction(command.isNonInteractive); - }); + .action(pushConnectorsAction); } diff --git a/packages/cli/src/cli/commands/dashboard/open.ts b/packages/cli/src/cli/commands/dashboard/open.ts index 83af7941..72a752cd 100644 --- a/packages/cli/src/cli/commands/dashboard/open.ts +++ b/packages/cli/src/cli/commands/dashboard/open.ts @@ -1,11 +1,11 @@ import type { Command } from "commander"; import open from "open"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, getDashboardUrl } from "@/cli/utils/index.js"; -async function openDashboard( - isNonInteractive: boolean, -): Promise { +async function openDashboard({ + isNonInteractive, +}: CLIContext): Promise { const dashboardUrl = getDashboardUrl(); if (!isNonInteractive) { @@ -18,7 +18,5 @@ async function openDashboard( export function getDashboardOpenCommand(): Command { return new Base44Command("open") .description("Open the app dashboard in your browser") - .action(async (_options: unknown, command: Base44Command) => { - return await openDashboard(command.isNonInteractive); - }); + .action(openDashboard); } diff --git a/packages/cli/src/cli/commands/dev.ts b/packages/cli/src/cli/commands/dev.ts index 7dc7b8de..f637da14 100644 --- a/packages/cli/src/cli/commands/dev.ts +++ b/packages/cli/src/cli/commands/dev.ts @@ -1,6 +1,6 @@ import type { Command } from "commander"; import { createDevServer } from "@/cli/dev/dev-server/main.js"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, theme } from "@/cli/utils/index.js"; import { getDenoWrapperPath } from "@/core/assets.js"; import { readProjectConfig } from "@/core/project/config.js"; @@ -9,9 +9,13 @@ interface DevOptions { port?: string; } -async function devAction(options: DevOptions): Promise { +async function devAction( + { log }: CLIContext, + options: DevOptions, +): Promise { const port = options.port ? Number(options.port) : undefined; const { port: resolvedPort } = await createDevServer({ + log, port, denoWrapperPath: getDenoWrapperPath(), loadResources: async () => { diff --git a/packages/cli/src/cli/commands/entities/push.ts b/packages/cli/src/cli/commands/entities/push.ts index ccc0eed8..5e0851b5 100644 --- a/packages/cli/src/cli/commands/entities/push.ts +++ b/packages/cli/src/cli/commands/entities/push.ts @@ -1,11 +1,12 @@ -import { log } from "@clack/prompts"; import { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; import { readProjectConfig } from "@/core/index.js"; import { pushEntities } from "@/core/resources/entity/index.js"; -async function pushEntitiesAction(): Promise { +async function pushEntitiesAction({ + log, +}: CLIContext): Promise { const { entities } = await readProjectConfig(); if (entities.length === 0) { diff --git a/packages/cli/src/cli/commands/exec.ts b/packages/cli/src/cli/commands/exec.ts index d49ca1dc..ca9f0f9f 100644 --- a/packages/cli/src/cli/commands/exec.ts +++ b/packages/cli/src/cli/commands/exec.ts @@ -1,5 +1,5 @@ import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command } from "@/cli/utils/index.js"; import { InvalidInputError } from "@/core/errors.js"; import { runScript } from "@/core/exec/index.js"; @@ -67,7 +67,7 @@ Examples: Inline script: $ echo "const users = await base44.entities.User.list()" | base44 exec`, ) - .action(async (_options: unknown, command: Base44Command) => { - return await execAction(command.isNonInteractive); + .action(async ({ isNonInteractive }: CLIContext) => { + return await execAction(isNonInteractive); }); } diff --git a/packages/cli/src/cli/commands/functions/delete.ts b/packages/cli/src/cli/commands/functions/delete.ts index 3f8b95ca..d8724b5e 100644 --- a/packages/cli/src/cli/commands/functions/delete.ts +++ b/packages/cli/src/cli/commands/functions/delete.ts @@ -1,10 +1,11 @@ import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.js"; import { ApiError } from "@/core/errors.js"; import { deleteSingleFunction } from "@/core/resources/function/api.js"; async function deleteFunctionsAction( + _ctx: CLIContext, names: string[], ): Promise { let deleted = 0; @@ -61,8 +62,8 @@ export function getDeleteCommand(): Command { .description("Delete deployed functions") .argument("", "Function names to delete") .hook("preAction", validateNames) - .action(async (rawNames: string[]) => { + .action(async (ctx: CLIContext, rawNames: string[]) => { const names = parseNames(rawNames); - return deleteFunctionsAction(names); + return deleteFunctionsAction(ctx, names); }); } diff --git a/packages/cli/src/cli/commands/functions/deploy.ts b/packages/cli/src/cli/commands/functions/deploy.ts index 1fd32924..b6beb023 100644 --- a/packages/cli/src/cli/commands/functions/deploy.ts +++ b/packages/cli/src/cli/commands/functions/deploy.ts @@ -1,8 +1,8 @@ -import { log } from "@clack/prompts"; +import type { Logger } from "@base44-cli/logger"; 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 type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, theme } from "@/cli/utils/index.js"; import { InvalidInputError } from "@/core/errors.js"; import { readProjectConfig } from "@/core/index.js"; @@ -29,7 +29,7 @@ function resolveFunctionsToDeploy( return allFunctions.filter((f) => names.includes(f.name)); } -function formatPruneResult(pruneResult: PruneResult): void { +function formatPruneResult(pruneResult: PruneResult, log: Logger): void { if (pruneResult.deleted) { log.success(`${pruneResult.name.padEnd(25)} deleted`); } else { @@ -37,7 +37,7 @@ function formatPruneResult(pruneResult: PruneResult): void { } } -function formatPruneSummary(pruneResults: PruneResult[]): void { +function formatPruneSummary(pruneResults: PruneResult[], log: Logger): void { if (pruneResults.length > 0) { const pruned = pruneResults.filter((r) => r.deleted).length; log.info(`${pruned} deleted`); @@ -57,6 +57,7 @@ function buildDeploySummary(results: SingleFunctionDeployResult[]): string { } async function deployFunctionsAction( + { log }: CLIContext, names: string[], options: { force?: boolean }, ): Promise { @@ -95,7 +96,7 @@ async function deployFunctionsAction( }, onResult: (result) => { completed++; - formatDeployResult(result); + formatDeployResult(result, log); }, }); @@ -120,9 +121,9 @@ async function deployFunctionsAction( ), ); }, - onResult: formatPruneResult, + onResult: (r) => formatPruneResult(r, log), }); - formatPruneSummary(pruneResults); + formatPruneSummary(pruneResults, log); } 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 ( + ctx: CLIContext, + rawNames: string[], + options: { force?: boolean }, + ) => { + const names = parseNames(rawNames); + return deployFunctionsAction(ctx, names, options); + }, + ); } diff --git a/packages/cli/src/cli/commands/functions/formatDeployResult.ts b/packages/cli/src/cli/commands/functions/formatDeployResult.ts index 5a4be23f..39406a20 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 "@base44-cli/logger"; import { theme } from "@/cli/utils/theme.js"; import type { SingleFunctionDeployResult } from "@/core/resources/function/deploy.js"; @@ -6,7 +6,10 @@ function formatDuration(ms: number): string { return `${(ms / 1000).toFixed(1)}s`; } -export function formatDeployResult(result: SingleFunctionDeployResult): void { +export function formatDeployResult( + result: SingleFunctionDeployResult, + log: Logger, +): void { const label = result.name.padEnd(25); if (result.status === "deployed") { const timing = result.durationMs diff --git a/packages/cli/src/cli/commands/functions/list.ts b/packages/cli/src/cli/commands/functions/list.ts index 81c453eb..e701eb89 100644 --- a/packages/cli/src/cli/commands/functions/list.ts +++ b/packages/cli/src/cli/commands/functions/list.ts @@ -1,10 +1,11 @@ -import { log } from "@clack/prompts"; import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask, theme } from "@/cli/utils/index.js"; import { listDeployedFunctions } from "@/core/resources/function/api.js"; -async function listFunctionsAction(): Promise { +async function listFunctionsAction({ + log, +}: CLIContext): Promise { const { functions } = await runTask( "Fetching functions...", async () => listDeployedFunctions(), diff --git a/packages/cli/src/cli/commands/functions/pull.ts b/packages/cli/src/cli/commands/functions/pull.ts index c4bcda59..ff12fa62 100644 --- a/packages/cli/src/cli/commands/functions/pull.ts +++ b/packages/cli/src/cli/commands/functions/pull.ts @@ -1,13 +1,13 @@ import { dirname, join } from "node:path"; -import { log } from "@clack/prompts"; import type { Command } from "commander"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, runTask } from "@/cli/utils/index.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( + { log }: CLIContext, name: string | undefined, ): Promise { const { project } = await readProjectConfig(); diff --git a/packages/cli/src/cli/commands/project/create.ts b/packages/cli/src/cli/commands/project/create.ts index b26ef101..6c31b2eb 100644 --- a/packages/cli/src/cli/commands/project/create.ts +++ b/packages/cli/src/cli/commands/project/create.ts @@ -1,10 +1,11 @@ import { basename, join, resolve } from "node:path"; +import type { Logger } from "@base44-cli/logger"; 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"; -import type { RunCommandResult } from "@/cli/types.js"; +import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command, getDashboardUrl, @@ -56,6 +57,7 @@ function validateNonInteractiveFlags(command: Command): void { async function createInteractive( options: CreateOptions, + log: Logger, ): Promise { const templates = await listTemplates(); const templateOptions: Option