Skip to content

Commit 2ae6a2f

Browse files
fix(cli): render success warnings in human output
Best-effort steps (local pin cleanup on remove/transfer) collected warnings that only reached the JSON envelope; human users saw a clean success even when their local binding was left stale. The command runner now renders success warnings as ⚠ lines in human mode for every command, so partial local-state failures are visible. Also registers the new project/transfer error codes in error-conventions.md.
1 parent c485e14 commit 2ae6a2f

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

docs/product/error-conventions.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ These codes are the minimum stable set for the MVP:
168168
- `PROJECT_SETUP_REQUIRED`
169169
- `PROJECT_LINK_TARGET_REQUIRED`
170170
- `PROJECT_CREATE_FAILED`
171+
- `PROJECT_RENAME_FAILED`
172+
- `PROJECT_REMOVE_BLOCKED`
173+
- `PROJECT_TRANSFER_REJECTED`
174+
- `PROJECT_API_ERROR`
175+
- `TRANSFER_RECIPIENT_REQUIRED`
176+
- `TRANSFER_RECIPIENT_UNAVAILABLE`
171177
- `PROJECT_NOT_FOUND`
172178
- `PROJECT_AMBIGUOUS`
173179
- `APP_AMBIGUOUS`
@@ -233,6 +239,12 @@ Recommended meanings:
233239
- `PROJECT_SETUP_REQUIRED`: command needs explicit or durable Project context before it can continue
234240
- `PROJECT_LINK_TARGET_REQUIRED`: `project link` needs the user to choose an existing Project or create a new one
235241
- `PROJECT_CREATE_FAILED`: Project creation failed before deployment or linking could continue
242+
- `PROJECT_RENAME_FAILED`: the platform rejected the new project name
243+
- `PROJECT_REMOVE_BLOCKED`: project removal is blocked while it still has active deployments
244+
- `PROJECT_TRANSFER_REJECTED`: the platform rejected the transfer, for example an invalid or expired recipient token
245+
- `PROJECT_API_ERROR`: project Management API request failed without a more specific CLI error code
246+
- `TRANSFER_RECIPIENT_REQUIRED`: project transfer needs --to-workspace or --recipient-token
247+
- `TRANSFER_RECIPIENT_UNAVAILABLE`: --to-workspace cannot resolve local OAuth sessions while PRISMA_SERVICE_TOKEN is set
236248
- `PROJECT_NOT_FOUND`: requested project does not exist or is not accessible
237249
- `PROJECT_AMBIGUOUS`: multiple safe project candidates matched
238250
- `APP_AMBIGUOUS`: multiple apps matched the inferred or explicit app target

packages/cli/src/shell/command-runner.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
writeJsonSuccess,
2121
} from "./output";
2222
import { type CliRuntime, createCommandContext } from "./runtime";
23+
import { renderSummaryLine } from "./ui";
2324

2425
interface CommandPresenter<T> {
2526
renderStdout?: (
@@ -126,11 +127,17 @@ async function writeCommandSuccess<T>(
126127
}
127128

128129
const rendered = presenter.renderHuman(context, descriptor, success.result);
130+
// Warnings are part of the success contract in JSON; render them in human
131+
// mode too so partial failures (best-effort cleanup, degraded steps) are
132+
// never silent.
133+
const warningLines = success.warnings.map((warning) =>
134+
renderSummaryLine(context.ui, "warning", warning),
135+
);
129136
const diagnostics = await renderBestEffortCommandDiagnostics(context, {
130137
enabled: context.flags.verbose && rendered.length > 0,
131138
durationMs,
132139
});
133-
const humanLines = [...rendered, ...diagnostics];
140+
const humanLines = [...rendered, ...warningLines, ...diagnostics];
134141
if (stdout.length > 0 && humanLines.length > 0) {
135142
humanLines.push("");
136143
}

packages/cli/tests/command-runner.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,32 @@ afterEach(() => {
8888
});
8989

9090
describe("command runner success output", () => {
91+
it("renders success warnings in human output", async () => {
92+
const { runtime, stderr } = await createRuntime(["project", "remove"]);
93+
94+
await runCommand(
95+
runtime,
96+
"project.remove",
97+
{},
98+
async () => ({
99+
command: "project.remove",
100+
result: { ok: true },
101+
warnings: [
102+
"The local pin .prisma/local.json points at the removed project but could not be deleted.",
103+
],
104+
nextSteps: [],
105+
}),
106+
{
107+
renderHuman: () => ["Project removed"],
108+
},
109+
);
110+
111+
expect(process.exitCode).toBeUndefined();
112+
expect(stderr.buffer).toContain("Project removed");
113+
expect(stderr.buffer).toContain("⚠");
114+
expect(stderr.buffer).toContain("could not be deleted");
115+
});
116+
91117
it("adds local diagnostics to successful verbose human output", async () => {
92118
const { runtime, stderr } = await createRuntime([
93119
"project",

0 commit comments

Comments
 (0)