Skip to content

Commit e18e83b

Browse files
feat(cli): add verbose success diagnostics (#69)
Signed-off-by: Aman Varshney <amanvarshney.work@gmail.com>
1 parent c7e7b86 commit e18e83b

30 files changed

Lines changed: 1159 additions & 69 deletions

packages/cli/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141
},
4242
"dependencies": {
4343
"@clack/prompts": "^1.5.0",
44-
"@prisma/compute-sdk": "^0.20.0",
44+
"@prisma/compute-sdk": "^0.21.0",
4545
"@prisma/credentials-store": "^7.8.0",
46-
"@prisma/management-api-sdk": "^1.35.0",
46+
"@prisma/management-api-sdk": "^1.37.0",
4747
"c12": "4.0.0-beta.5",
4848
"colorette": "^2.0.20",
4949
"commander": "^14.0.3",

packages/cli/src/cli.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getCliName, getCliVersion } from "./lib/version";
1313
import { attachCommandDescriptor } from "./shell/command-meta";
1414
import { CliError } from "./shell/errors";
1515
import { addCompactGlobalFlags } from "./shell/global-flags";
16-
import { writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output";
16+
import { formatUnexpectedError, writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output";
1717
import { disposePromptState } from "./shell/prompt";
1818
import { configureRuntimeCommand, createCommandContext, type CliRuntime } from "./shell/runtime";
1919
import { createShellUi } from "./shell/ui";
@@ -49,8 +49,7 @@ export async function runCli(options: RunCliOptions = {}): Promise<number> {
4949
return error.code === "commander.helpDisplayed" ? 0 : 2;
5050
}
5151

52-
const message = error instanceof Error ? error.stack ?? error.message : String(error);
53-
runtime.stderr.write(`${message}\n`);
52+
runtime.stderr.write(formatUnexpectedError(error, runtime.argv.includes("--trace")));
5453
return 1;
5554
} finally {
5655
disposePromptState(runtime.stdin);

packages/cli/src/controllers/app-env-file.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { CommandSuccess } from "../shell/output";
77
import type { CommandContext } from "../shell/runtime";
88
import type {
99
EnvAddResult,
10+
EnvResolvedContext,
1011
EnvUpdateResult,
1112
EnvVariableMetadata,
1213
} from "../types/app-env";
@@ -29,6 +30,7 @@ export async function runEnvAddFile(
2930
resolved: ResolvedEnvFileScope,
3031
filePath: string,
3132
assignments: EnvFileAssignment[],
33+
verboseContext: EnvResolvedContext,
3234
): Promise<CommandSuccess<EnvAddResult>> {
3335
const existing = await findVariablesByNaturalKey(
3436
client,
@@ -96,6 +98,7 @@ export async function runEnvAddFile(
9698
command: "project.env.add",
9799
result: {
98100
projectId,
101+
verboseContext,
99102
scope: resolved.descriptor,
100103
variables,
101104
file: {
@@ -115,6 +118,7 @@ export async function runEnvUpdateFile(
115118
resolved: ResolvedEnvFileScope,
116119
filePath: string,
117120
assignments: EnvFileAssignment[],
121+
verboseContext: EnvResolvedContext,
118122
): Promise<CommandSuccess<EnvUpdateResult>> {
119123
const existing = await findVariablesByNaturalKey(
120124
client,
@@ -172,6 +176,7 @@ export async function runEnvUpdateFile(
172176
command: "project.env.update",
173177
result: {
174178
projectId,
179+
verboseContext,
175180
scope: resolved.descriptor,
176181
variables,
177182
file: {

packages/cli/src/controllers/app-env.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
EnvListTarget,
2020
EnvListResult,
2121
EnvRmResult,
22+
EnvResolvedContext,
2223
EnvScopeDescriptor,
2324
EnvUpdateResult,
2425
} from "../types/app-env";
@@ -92,14 +93,14 @@ export async function runEnvAdd(
9293
}
9394

9495
const input = await resolveEnvWriteInput(context, source, "add");
95-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env add");
96+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env add");
9697
const resolved = await resolveScopeToApi(client, projectId, scope, {
9798
createBranchIfMissing: true,
9899
signal: context.runtime.signal,
99100
});
100101

101102
if (input.kind === "file") {
102-
return runEnvAddFile(context, client, projectId, resolved, input.filePath, input.assignments);
103+
return runEnvAddFile(context, client, projectId, resolved, input.filePath, input.assignments, verboseContext);
103104
}
104105

105106
const existing = await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal);
@@ -121,7 +122,6 @@ export async function runEnvAdd(
121122
const warnings =
122123
scope.kind === "branch" &&
123124
!(await findVariableByNaturalKey(client, projectId, input.key, {
124-
scope: { kind: "role", role: "preview" },
125125
descriptor: { kind: "role", role: "preview" },
126126
apiTarget: { class: "preview", branchId: null },
127127
}, context.runtime.signal))
@@ -153,6 +153,7 @@ export async function runEnvAdd(
153153
command: "project.env.add",
154154
result: {
155155
projectId,
156+
verboseContext,
156157
scope: resolved.descriptor,
157158
variable: toMetadata(data.data as RawEnvironmentVariable, resolved.descriptor),
158159
},
@@ -179,14 +180,14 @@ export async function runEnvUpdate(
179180
}
180181

181182
const input = await resolveEnvWriteInput(context, source, "update");
182-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env update");
183+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env update");
183184
const resolved = await resolveScopeToApi(client, projectId, scope, {
184185
createBranchIfMissing: false,
185186
signal: context.runtime.signal,
186187
});
187188

188189
if (input.kind === "file") {
189-
return runEnvUpdateFile(context, client, projectId, resolved, input.filePath, input.assignments);
190+
return runEnvUpdateFile(context, client, projectId, resolved, input.filePath, input.assignments, verboseContext);
190191
}
191192

192193
const existing = await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal);
@@ -221,6 +222,7 @@ export async function runEnvUpdate(
221222
command: "project.env.update",
222223
result: {
223224
projectId,
225+
verboseContext,
224226
scope: resolved.descriptor,
225227
variable: toMetadata(data.data as RawEnvironmentVariable, resolved.descriptor),
226228
},
@@ -301,8 +303,8 @@ export async function runEnvList(
301303
): Promise<CommandSuccess<EnvListResult>> {
302304
const explicit = resolveEnvScope(flags, { requireExplicit: false, command: "list" });
303305

304-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
305-
const resolved = await resolveListScopeToApi(client, projectId, explicit, {
306+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env list");
307+
const resolved = await resolveListScopeToApi(client, projectId, explicit ?? undefined, {
306308
cwd: context.runtime.cwd,
307309
signal: context.runtime.signal,
308310
});
@@ -318,6 +320,7 @@ export async function runEnvList(
318320
command: "project.env.list",
319321
result: {
320322
projectId,
323+
verboseContext,
321324
scope: resolved.descriptor,
322325
target: resolved.target,
323326
variables: variables.map((row) => toMetadata(row, resolved.descriptor)),
@@ -355,7 +358,7 @@ export async function runEnvRemove(
355358
);
356359
}
357360

358-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env remove");
361+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env remove");
359362
const resolved = await resolveScopeToApi(client, projectId, scope, {
360363
createBranchIfMissing: false,
361364
signal: context.runtime.signal,
@@ -390,6 +393,7 @@ export async function runEnvRemove(
390393
command: "project.env.remove",
391394
result: {
392395
projectId,
396+
verboseContext,
393397
scope: resolved.descriptor,
394398
key,
395399
},
@@ -402,7 +406,7 @@ async function requireClientAndProject(
402406
context: CommandContext,
403407
explicitProject: string | undefined,
404408
commandName: string,
405-
): Promise<{ client: ManagementApiClient; projectId: string }> {
409+
): Promise<{ client: ManagementApiClient; projectId: string; verboseContext: EnvResolvedContext }> {
406410
const authState = await requireAuthenticatedAuthState(context);
407411
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
408412
if (!client) {
@@ -420,7 +424,15 @@ async function requireClientAndProject(
420424
commandName,
421425
});
422426

423-
return { client, projectId: target.project.id };
427+
return {
428+
client,
429+
projectId: target.project.id,
430+
verboseContext: {
431+
workspace: authState.workspace,
432+
project: target.project,
433+
resolution: target.resolution,
434+
},
435+
};
424436
}
425437

426438
async function resolveScopeToApi(

packages/cli/src/controllers/app.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type {
2727
AppOpenResult,
2828
AppPromoteResult,
2929
AppRemoveResult,
30+
AppResolvedContext,
3031
AppRollbackResult,
3132
AppShowResult,
3233
AppRunResult,
@@ -38,7 +39,7 @@ import type { ProjectResolution, ProjectSummary } from "../types/project";
3839
import { requireComputeAuth } from "../lib/auth/guard";
3940
import { readAuthState } from "../lib/auth/auth-ops";
4041
import { getApiBaseUrl, SERVICE_TOKEN_ENV_VAR } from "../lib/auth/client";
41-
import { parseEnvAssignments } from "../lib/app/env-vars";
42+
import { envVarNames, parseEnvAssignments } from "../lib/app/env-vars";
4243
import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output";
4344
import {
4445
DEFAULT_LOCAL_DEV_PORT,
@@ -367,6 +368,18 @@ export async function runAppDeploy(
367368
name: deployResult.app.name,
368369
},
369370
deployment: deployResult.deployment,
371+
deploySettings: {
372+
framework: {
373+
key: framework.key,
374+
buildType,
375+
name: framework.displayName,
376+
source: framework.annotation,
377+
},
378+
entrypoint: entrypoint ?? null,
379+
httpPort: runtime.port,
380+
region: selectedApp.region ?? null,
381+
envVars: envVarNames(envVars),
382+
},
370383
durationMs: deployDurationMs,
371384
localPin: localPinResult,
372385
},
@@ -393,6 +406,7 @@ export async function runAppListDeploys(
393406
command: "app.list-deploys",
394407
result: {
395408
projectId,
409+
verboseContext: toAppVerboseContext(target),
396410
app: null,
397411
deployments: [],
398412
},
@@ -423,6 +437,7 @@ export async function runAppListDeploys(
423437
command: "app.list-deploys",
424438
result: {
425439
projectId,
440+
verboseContext: toAppVerboseContext(target),
426441
app: {
427442
id: deploymentsResult.app.id,
428443
name: deploymentsResult.app.name,
@@ -454,6 +469,7 @@ export async function runAppShow(
454469
command: "app.show",
455470
result: {
456471
projectId,
472+
verboseContext: toAppVerboseContext(target),
457473
app: null,
458474
liveDeployment: null,
459475
liveUrl: null,
@@ -489,6 +505,7 @@ export async function runAppShow(
489505
command: "app.show",
490506
result: {
491507
projectId,
508+
verboseContext: toAppVerboseContext(target),
492509
app: {
493510
id: deploymentsResult.app.id,
494511
name: deploymentsResult.app.name,
@@ -625,6 +642,7 @@ export async function runAppOpen(
625642
command: "app.open",
626643
result: {
627644
projectId,
645+
verboseContext: toAppVerboseContext(target),
628646
app: {
629647
id: deploymentsResult.app.id,
630648
name: deploymentsResult.app.name,
@@ -1094,6 +1112,7 @@ export async function runAppPromote(
10941112
command: "app.promote",
10951113
result: {
10961114
projectId,
1115+
verboseContext: toAppVerboseContext(target),
10971116
app: {
10981117
id: deploymentsResult.app.id,
10991118
name: deploymentsResult.app.name,
@@ -1164,6 +1183,7 @@ export async function runAppRollback(
11641183
command: "app.rollback",
11651184
result: {
11661185
projectId,
1186+
verboseContext: toAppVerboseContext(target),
11671187
app: {
11681188
id: deploymentsResult.app.id,
11691189
name: deploymentsResult.app.name,
@@ -1205,6 +1225,7 @@ export async function runAppRemove(
12051225
command: "app.remove",
12061226
result: {
12071227
projectId,
1228+
verboseContext: toAppVerboseContext(target),
12081229
app: {
12091230
id: removedApp.id,
12101231
name: removedApp.name,
@@ -2493,11 +2514,21 @@ function toBranchKind(name: string): BranchKind {
24932514

24942515
function toResultBranch(branch: ResolvedAppProjectContext["branch"]): AppDeployResult["branch"] {
24952516
return {
2517+
id: branch.id,
24962518
name: branch.name,
24972519
kind: branch.kind,
24982520
};
24992521
}
25002522

2523+
function toAppVerboseContext(target: ResolvedAppProjectContext): AppResolvedContext {
2524+
return {
2525+
workspace: target.workspace,
2526+
project: target.project,
2527+
branch: target.branch,
2528+
resolution: target.resolution,
2529+
};
2530+
}
2531+
25012532
function toBranchDatabaseDeployBranch(branch: ResolvedAppProjectContext["branch"]): BranchDatabaseDeployBranch {
25022533
if (!branch.id) {
25032534
throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);

packages/cli/src/controllers/branch.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ async function listRealBranches(context: CommandContext): Promise<BranchListResu
6868
return {
6969
projectId: target.project.id,
7070
projectName: target.project.name,
71+
verboseContext: {
72+
workspace,
73+
project: target.project,
74+
resolution: target.resolution,
75+
},
7176
branches: sortBranches(branches.map(toBranchSummary)),
7277
};
7378
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { resolveLocalStateFilePath } from "../adapters/local-state";
2+
import { resolveStateDir, type CommandContext } from "../shell/runtime";
3+
import type { CommandDiagnostics } from "../types/diagnostics";
4+
import { readLocalGitState } from "./git/local-status";
5+
6+
export async function collectCommandDiagnostics(
7+
context: CommandContext,
8+
options: { durationMs?: number } = {},
9+
): Promise<CommandDiagnostics> {
10+
const stateDir = resolveStateDir(context.runtime);
11+
12+
return {
13+
cwd: context.runtime.cwd,
14+
stateFilePath: resolveLocalStateFilePath(stateDir),
15+
git: await readLocalGitState(context.runtime.cwd, context.runtime.signal),
16+
durationMs: options.durationMs,
17+
};
18+
}

0 commit comments

Comments
 (0)