diff --git a/docs/commands.md b/docs/commands.md index c6516a577..834b1b917 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -12,6 +12,9 @@ JSON error envelope. Successful results use `RunCommandResult.stdout`; progress and server warnings use the context logger. Deployment reads project resources; deletion only needs the selected app context. +Function and actor deploy responses carry non-fatal server `warnings`. Keep them +in the response schema; `formatDeployResult` prints them under each result line. + ## Branch targeting Discover names with `base44 branches list --app-id --json`. diff --git a/packages/cli/src/cli/commands/actors/deploy.ts b/packages/cli/src/cli/commands/actors/deploy.ts index d3c45df2b..021629797 100644 --- a/packages/cli/src/cli/commands/actors/deploy.ts +++ b/packages/cli/src/cli/commands/actors/deploy.ts @@ -45,9 +45,6 @@ async function deployActorsAction( onResult: (result) => { completed++; formatDeployResult(result, log); - if (result.status !== "error") - for (const warning of result.warnings) - log.warn(`${result.name}: ${warning}`); }, }); const summary = { diff --git a/packages/cli/src/cli/commands/functions/formatDeployResult.ts b/packages/cli/src/cli/commands/functions/formatDeployResult.ts index 39406a203..2c1d66089 100644 --- a/packages/cli/src/cli/commands/functions/formatDeployResult.ts +++ b/packages/cli/src/cli/commands/functions/formatDeployResult.ts @@ -20,5 +20,10 @@ export function formatDeployResult( log.success(`${label} unchanged`); } else { log.error(`${label} error: ${result.error}`); + return; + } + for (const warning of result.warnings ?? []) { + const prefix = `${result.name}: `; + log.warn(warning.startsWith(prefix) ? warning : `${prefix}${warning}`); } } diff --git a/packages/cli/src/cli/commands/project/deploy.ts b/packages/cli/src/cli/commands/project/deploy.ts index c3d0c7a4b..5b21541ba 100644 --- a/packages/cli/src/cli/commands/project/deploy.ts +++ b/packages/cli/src/cli/commands/project/deploy.ts @@ -152,9 +152,6 @@ export async function deployAction( onActorResult: (result) => { actorCompleted++; formatDeployResult(result, log); - if (result.status !== "error") - for (const warning of result.warnings) - log.warn(`${result.name}: ${warning}`); }, }); diff --git a/packages/cli/src/core/resources/function/deploy.ts b/packages/cli/src/core/resources/function/deploy.ts index c0e3f66bf..f8a566e34 100644 --- a/packages/cli/src/core/resources/function/deploy.ts +++ b/packages/cli/src/core/resources/function/deploy.ts @@ -30,6 +30,7 @@ export interface SingleFunctionDeployResult { status: "deployed" | "unchanged" | "error"; error?: string | null; durationMs?: number; + warnings?: string[]; } async function deployOne( @@ -47,6 +48,7 @@ async function deployOne( name: functionWithCode.name, status: response.status, durationMs: Date.now() - start, + warnings: response.warnings, }; } catch (error) { return { diff --git a/packages/cli/src/core/resources/function/schema.ts b/packages/cli/src/core/resources/function/schema.ts index c5610524d..5f56dc3ec 100644 --- a/packages/cli/src/core/resources/function/schema.ts +++ b/packages/cli/src/core/resources/function/schema.ts @@ -152,6 +152,7 @@ const BackendFunctionSchema = FunctionConfigSchema.extend({ export const DeploySingleFunctionResponseSchema = z.object({ status: z.enum(["deployed", "unchanged"]), + warnings: z.array(z.string()).optional().default([]), }); const FunctionInfoSchema = z diff --git a/packages/cli/tests/cli/deploy.spec.ts b/packages/cli/tests/cli/deploy.spec.ts index 3369634e8..7f94c1745 100644 --- a/packages/cli/tests/cli/deploy.spec.ts +++ b/packages/cli/tests/cli/deploy.spec.ts @@ -229,6 +229,26 @@ describe("deploy command (unified)", () => { t.expectResult(result).toContain("App deployed successfully"); }); + it("prints function deploy warnings", async () => { + await t.givenLoggedInWithProject(fixture("with-functions-and-entities")); + t.api.mockEntitiesPush({ created: ["Order"], updated: [], deleted: [] }); + t.api.mockSingleFunctionDeploy({ + status: "deployed", + warnings: ["process-order: automations not created (nightly)."], + }); + t.api.mockAgentsPush({ created: [], updated: [], deleted: [] }); + t.api.mockConnectorsList({ integrations: [] }); + t.api.mockStripeStatus({ stripe_mode: null }); + + const result = await t.run("deploy", "-y"); + + t.expectResult(result).toSucceed(); + t.expectResult(result).toContain( + "process-order: automations not created (nightly).", + ); + t.expectResult(result).toContain("App deployed successfully"); + }); + it("deploys zero-config functions (path-based names) with unified deploy", async () => { await t.givenLoggedInWithProject(fixture("with-zero-config-functions")); t.api.mockEntitiesPush({ created: [], updated: [], deleted: [] }); diff --git a/packages/cli/tests/cli/functions_deploy.spec.ts b/packages/cli/tests/cli/functions_deploy.spec.ts index 573119202..4634f5627 100644 --- a/packages/cli/tests/cli/functions_deploy.spec.ts +++ b/packages/cli/tests/cli/functions_deploy.spec.ts @@ -57,6 +57,41 @@ describe("functions deploy command", () => { t.expectResult(result).toContain("1 unchanged"); }); + it("prints server warnings for a deployed function", async () => { + await t.givenLoggedInWithProject(fixture("with-functions-and-entities")); + t.api.mockSingleFunctionDeploy({ + status: "deployed", + warnings: [ + "process-order: automations not created (nightly) and none removed.", + "Private data source warehouse is unavailable.", + ], + }); + + const result = await t.run("functions", "deploy"); + + t.expectResult(result).toSucceed(); + t.expectResult(result).toContain( + "process-order: automations not created (nightly) and none removed.", + ); + t.expectResult(result).toNotContain("process-order: process-order:"); + t.expectResult(result).toContain( + "process-order: Private data source warehouse is unavailable.", + ); + }); + + it("prints server warnings for an unchanged function", async () => { + await t.givenLoggedInWithProject(fixture("with-functions-and-entities")); + t.api.mockSingleFunctionDeploy({ + status: "unchanged", + warnings: ["Automations are retired."], + }); + + const result = await t.run("functions", "deploy"); + + t.expectResult(result).toSucceed(); + t.expectResult(result).toContain("process-order: Automations are retired."); + }); + it("deploys zero-config and path-named functions successfully", async () => { await t.givenLoggedInWithProject(fixture("with-zero-config-functions")); t.api.mockSingleFunctionDeploy({ status: "deployed" }); diff --git a/packages/cli/tests/cli/testkit/TestAPIServer.ts b/packages/cli/tests/cli/testkit/TestAPIServer.ts index 0ebdba5bb..6ad596c43 100644 --- a/packages/cli/tests/cli/testkit/TestAPIServer.ts +++ b/packages/cli/tests/cli/testkit/TestAPIServer.ts @@ -113,6 +113,7 @@ interface SecretsDeleteResponse { interface SingleFunctionDeployResponse { status: "deployed" | "unchanged"; + warnings?: string[]; } interface AutomationBase {