@prisma/cli 3.0.0-dev.62.1 → 3.0.0-dev.65.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -141,4 +141,4 @@ var LocalStateStore = class {
141
141
  }
142
142
  };
143
143
  //#endregion
144
- export { LocalStateStore };
144
+ export { LocalStateStore, resolveLocalStateFilePath };
package/dist/cli2.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CliError } from "./shell/errors.js";
2
2
  import { createShellUi } from "./shell/ui.js";
3
- import { writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
3
+ import { formatUnexpectedError, writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
4
4
  import { attachCommandDescriptor } from "./shell/command-meta.js";
5
5
  import { addCompactGlobalFlags } from "./shell/global-flags.js";
6
6
  import { configureRuntimeCommand, createCommandContext } from "./shell/runtime.js";
@@ -33,8 +33,7 @@ async function runCli(options = {}) {
33
33
  return process.exitCode ?? 0;
34
34
  } catch (error) {
35
35
  if (error instanceof CommanderError) return error.code === "commander.helpDisplayed" ? 0 : 2;
36
- const message = error instanceof Error ? error.stack ?? error.message : String(error);
37
- runtime.stderr.write(`${message}\n`);
36
+ runtime.stderr.write(formatUnexpectedError(error, runtime.argv.includes("--trace")));
38
37
  return 1;
39
38
  } finally {
40
39
  runtime.stdin;
@@ -2,7 +2,7 @@ import { CliError } from "../shell/errors.js";
2
2
  import { formatScopeLabel } from "../lib/app/env-config.js";
3
3
  import { apiCallError, findVariableByNaturalKey, toMetadata } from "./app-env-api.js";
4
4
  //#region src/controllers/app-env-file.ts
5
- async function runEnvAddFile(context, client, projectId, resolved, filePath, assignments) {
5
+ async function runEnvAddFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
6
6
  const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
7
7
  const existingKeys = assignments.map((assignment) => assignment.key).filter((key) => existing.has(key));
8
8
  if (existingKeys.length > 0) throw new CliError({
@@ -40,6 +40,7 @@ async function runEnvAddFile(context, client, projectId, resolved, filePath, ass
40
40
  command: "project.env.add",
41
41
  result: {
42
42
  projectId,
43
+ verboseContext,
43
44
  scope: resolved.descriptor,
44
45
  variables,
45
46
  file: {
@@ -51,7 +52,7 @@ async function runEnvAddFile(context, client, projectId, resolved, filePath, ass
51
52
  nextSteps: []
52
53
  };
53
54
  }
54
- async function runEnvUpdateFile(context, client, projectId, resolved, filePath, assignments) {
55
+ async function runEnvUpdateFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
55
56
  const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
56
57
  const missingKeys = assignments.map((assignment) => assignment.key).filter((key) => !existing.has(key));
57
58
  if (missingKeys.length > 0) throw new CliError({
@@ -87,6 +88,7 @@ async function runEnvUpdateFile(context, client, projectId, resolved, filePath,
87
88
  command: "project.env.update",
88
89
  result: {
89
90
  projectId,
91
+ verboseContext,
90
92
  scope: resolved.descriptor,
91
93
  variables,
92
94
  file: {
@@ -17,12 +17,12 @@ async function runEnvAdd(context, rawAssignment, flags) {
17
17
  });
18
18
  if (!scope) throw usageError(`prisma-cli project env add requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", ["prisma-cli project env add KEY=value --role production"], "app");
19
19
  const input = await resolveEnvWriteInput(context, source, "add");
20
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env add");
20
+ const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env add");
21
21
  const resolved = await resolveScopeToApi(client, projectId, scope, {
22
22
  createBranchIfMissing: true,
23
23
  signal: context.runtime.signal
24
24
  });
25
- if (input.kind === "file") return runEnvAddFile(context, client, projectId, resolved, input.filePath, input.assignments);
25
+ if (input.kind === "file") return runEnvAddFile(context, client, projectId, resolved, input.filePath, input.assignments, verboseContext);
26
26
  if (await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal)) throw new CliError({
27
27
  code: "ENV_VARIABLE_ALREADY_EXISTS",
28
28
  domain: "app",
@@ -33,10 +33,6 @@ async function runEnvAdd(context, rawAssignment, flags) {
33
33
  nextSteps: [`prisma-cli project env update ${input.key}=<new-value> ${formatScopeFlag(scope)}`]
34
34
  });
35
35
  const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(client, projectId, input.key, {
36
- scope: {
37
- kind: "role",
38
- role: "preview"
39
- },
40
36
  descriptor: {
41
37
  kind: "role",
42
38
  role: "preview"
@@ -61,6 +57,7 @@ async function runEnvAdd(context, rawAssignment, flags) {
61
57
  command: "project.env.add",
62
58
  result: {
63
59
  projectId,
60
+ verboseContext,
64
61
  scope: resolved.descriptor,
65
62
  variable: toMetadata(data.data, resolved.descriptor)
66
63
  },
@@ -76,12 +73,12 @@ async function runEnvUpdate(context, rawAssignment, flags) {
76
73
  });
77
74
  if (!scope) throw usageError(`prisma-cli project env update requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", ["prisma-cli project env update KEY=value --role production"], "app");
78
75
  const input = await resolveEnvWriteInput(context, source, "update");
79
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env update");
76
+ const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env update");
80
77
  const resolved = await resolveScopeToApi(client, projectId, scope, {
81
78
  createBranchIfMissing: false,
82
79
  signal: context.runtime.signal
83
80
  });
84
- if (input.kind === "file") return runEnvUpdateFile(context, client, projectId, resolved, input.filePath, input.assignments);
81
+ if (input.kind === "file") return runEnvUpdateFile(context, client, projectId, resolved, input.filePath, input.assignments, verboseContext);
85
82
  const existing = await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal);
86
83
  if (!existing) throw new CliError({
87
84
  code: "ENV_VARIABLE_NOT_FOUND",
@@ -102,6 +99,7 @@ async function runEnvUpdate(context, rawAssignment, flags) {
102
99
  command: "project.env.update",
103
100
  result: {
104
101
  projectId,
102
+ verboseContext,
105
103
  scope: resolved.descriptor,
106
104
  variable: toMetadata(data.data, resolved.descriptor)
107
105
  },
@@ -140,8 +138,8 @@ async function runEnvList(context, flags) {
140
138
  requireExplicit: false,
141
139
  command: "list"
142
140
  });
143
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
144
- const resolved = await resolveListScopeToApi(client, projectId, explicit, {
141
+ const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env list");
142
+ const resolved = await resolveListScopeToApi(client, projectId, explicit ?? void 0, {
145
143
  cwd: context.runtime.cwd,
146
144
  signal: context.runtime.signal
147
145
  });
@@ -154,6 +152,7 @@ async function runEnvList(context, flags) {
154
152
  command: "project.env.list",
155
153
  result: {
156
154
  projectId,
155
+ verboseContext,
157
156
  scope: resolved.descriptor,
158
157
  target: resolved.target,
159
158
  variables: variables.map((row) => toMetadata(row, resolved.descriptor))
@@ -169,7 +168,7 @@ async function runEnvRemove(context, key, flags) {
169
168
  command: "remove"
170
169
  });
171
170
  if (!scope) throw usageError("prisma-cli project env remove requires --role or --branch", "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env remove ${key} --role production`], "app");
172
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env remove");
171
+ const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env remove");
173
172
  const resolved = await resolveScopeToApi(client, projectId, scope, {
174
173
  createBranchIfMissing: false,
175
174
  signal: context.runtime.signal
@@ -193,6 +192,7 @@ async function runEnvRemove(context, key, flags) {
193
192
  command: "project.env.remove",
194
193
  result: {
195
194
  projectId,
195
+ verboseContext,
196
196
  scope: resolved.descriptor,
197
197
  key
198
198
  },
@@ -205,15 +205,21 @@ async function requireClientAndProject(context, explicitProject, commandName) {
205
205
  const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
206
206
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
207
207
  if (!authState.workspace) throw workspaceRequiredError();
208
+ const target = await resolveProjectTarget({
209
+ context,
210
+ workspace: authState.workspace,
211
+ explicitProject,
212
+ listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
213
+ commandName
214
+ });
208
215
  return {
209
216
  client,
210
- projectId: (await resolveProjectTarget({
211
- context,
217
+ projectId: target.project.id,
218
+ verboseContext: {
212
219
  workspace: authState.workspace,
213
- explicitProject,
214
- listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
215
- commandName
216
- })).project.id
220
+ project: target.project,
221
+ resolution: target.resolution
222
+ }
217
223
  };
218
224
  }
219
225
  async function resolveScopeToApi(client, projectId, scope, options) {
@@ -7,7 +7,7 @@ import { canPrompt } from "../shell/runtime.js";
7
7
  import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
8
8
  import { requireComputeAuth } from "../lib/auth/guard.js";
9
9
  import { readAuthState } from "../lib/auth/auth-ops.js";
10
- import { parseEnvAssignments } from "../lib/app/env-vars.js";
10
+ import { envVarNames, parseEnvAssignments } from "../lib/app/env-vars.js";
11
11
  import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
12
12
  import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
13
13
  import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
@@ -213,6 +213,18 @@ async function runAppDeploy(context, appName, options) {
213
213
  name: deployResult.app.name
214
214
  },
215
215
  deployment: deployResult.deployment,
216
+ deploySettings: {
217
+ framework: {
218
+ key: framework.key,
219
+ buildType,
220
+ name: framework.displayName,
221
+ source: framework.annotation
222
+ },
223
+ entrypoint: entrypoint ?? null,
224
+ httpPort: runtime.port,
225
+ region: selectedApp.region ?? null,
226
+ envVars: envVarNames(envVars)
227
+ },
216
228
  durationMs: deployDurationMs,
217
229
  localPin: localPinResult
218
230
  },
@@ -228,6 +240,7 @@ async function runAppListDeploys(context, appName, projectRef) {
228
240
  command: "app.list-deploys",
229
241
  result: {
230
242
  projectId,
243
+ verboseContext: toAppVerboseContext(target),
231
244
  app: null,
232
245
  deployments: []
233
246
  },
@@ -247,6 +260,7 @@ async function runAppListDeploys(context, appName, projectRef) {
247
260
  command: "app.list-deploys",
248
261
  result: {
249
262
  projectId,
263
+ verboseContext: toAppVerboseContext(target),
250
264
  app: {
251
265
  id: deploymentsResult.app.id,
252
266
  name: deploymentsResult.app.name
@@ -265,6 +279,7 @@ async function runAppShow(context, appName, projectRef) {
265
279
  command: "app.show",
266
280
  result: {
267
281
  projectId,
282
+ verboseContext: toAppVerboseContext(target),
268
283
  app: null,
269
284
  liveDeployment: null,
270
285
  liveUrl: null,
@@ -287,6 +302,7 @@ async function runAppShow(context, appName, projectRef) {
287
302
  command: "app.show",
288
303
  result: {
289
304
  projectId,
305
+ verboseContext: toAppVerboseContext(target),
290
306
  app: {
291
307
  id: deploymentsResult.app.id,
292
308
  name: deploymentsResult.app.name
@@ -360,6 +376,7 @@ async function runAppOpen(context, appName, projectRef) {
360
376
  command: "app.open",
361
377
  result: {
362
378
  projectId,
379
+ verboseContext: toAppVerboseContext(target),
363
380
  app: {
364
381
  id: deploymentsResult.app.id,
365
382
  name: deploymentsResult.app.name
@@ -650,6 +667,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
650
667
  command: "app.promote",
651
668
  result: {
652
669
  projectId,
670
+ verboseContext: toAppVerboseContext(target),
653
671
  app: {
654
672
  id: deploymentsResult.app.id,
655
673
  name: deploymentsResult.app.name
@@ -692,6 +710,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
692
710
  command: "app.rollback",
693
711
  result: {
694
712
  projectId,
713
+ verboseContext: toAppVerboseContext(target),
695
714
  app: {
696
715
  id: deploymentsResult.app.id,
697
716
  name: deploymentsResult.app.name
@@ -720,6 +739,7 @@ async function runAppRemove(context, appName, projectRef) {
720
739
  command: "app.remove",
721
740
  result: {
722
741
  projectId,
742
+ verboseContext: toAppVerboseContext(target),
723
743
  app: {
724
744
  id: removedApp.id,
725
745
  name: removedApp.name
@@ -1464,10 +1484,19 @@ function toBranchKind(name) {
1464
1484
  }
1465
1485
  function toResultBranch(branch) {
1466
1486
  return {
1487
+ id: branch.id,
1467
1488
  name: branch.name,
1468
1489
  kind: branch.kind
1469
1490
  };
1470
1491
  }
1492
+ function toAppVerboseContext(target) {
1493
+ return {
1494
+ workspace: target.workspace,
1495
+ project: target.project,
1496
+ branch: target.branch,
1497
+ resolution: target.resolution
1498
+ };
1499
+ }
1471
1500
  function toBranchDatabaseDeployBranch(branch) {
1472
1501
  if (!branch.id) throw new Error(`Deploy branch "${branch.name}" was not resolved remotely.`);
1473
1502
  return {
@@ -41,6 +41,11 @@ async function listRealBranches(context) {
41
41
  return {
42
42
  projectId: target.project.id,
43
43
  projectName: target.project.name,
44
+ verboseContext: {
45
+ workspace,
46
+ project: target.project,
47
+ resolution: target.resolution
48
+ },
44
49
  branches: sortBranches(branches.map(toBranchSummary))
45
50
  };
46
51
  }
@@ -0,0 +1,15 @@
1
+ import { resolveLocalStateFilePath } from "../adapters/local-state.js";
2
+ import { resolveStateDir } from "../shell/runtime.js";
3
+ import { readLocalGitState } from "./git/local-status.js";
4
+ //#region src/lib/diagnostics.ts
5
+ async function collectCommandDiagnostics(context, options = {}) {
6
+ const stateDir = resolveStateDir(context.runtime);
7
+ return {
8
+ cwd: context.runtime.cwd,
9
+ stateFilePath: resolveLocalStateFilePath(stateDir),
10
+ git: await readLocalGitState(context.runtime.cwd, context.runtime.signal),
11
+ durationMs: options.durationMs
12
+ };
13
+ }
14
+ //#endregion
15
+ export { collectCommandDiagnostics };
@@ -0,0 +1,57 @@
1
+ import { execFile } from "node:child_process";
2
+ //#region src/lib/git/local-status.ts
3
+ async function readLocalGitState(cwd, signal) {
4
+ signal.throwIfAborted();
5
+ if ((await runGit(cwd, ["rev-parse", "--is-inside-work-tree"], signal))?.trim() !== "true") return null;
6
+ const [ref, sha, status] = await Promise.all([
7
+ runGit(cwd, [
8
+ "symbolic-ref",
9
+ "--quiet",
10
+ "--short",
11
+ "HEAD"
12
+ ], signal),
13
+ runGit(cwd, [
14
+ "rev-parse",
15
+ "--short",
16
+ "HEAD"
17
+ ], signal),
18
+ runGit(cwd, ["status", "--porcelain"], signal)
19
+ ]);
20
+ return {
21
+ ref: cleanGitValue(ref),
22
+ sha: cleanGitValue(sha),
23
+ dirty: status === null ? null : status.trim().length > 0
24
+ };
25
+ }
26
+ function runGit(cwd, args, signal) {
27
+ return new Promise((resolve, reject) => {
28
+ signal.throwIfAborted();
29
+ execFile("git", args, {
30
+ cwd,
31
+ signal,
32
+ timeout: 2e3
33
+ }, (error, stdout) => {
34
+ if (signal.aborted) {
35
+ reject(error);
36
+ return;
37
+ }
38
+ if (error) {
39
+ resolve(null);
40
+ return;
41
+ }
42
+ resolve(stdout);
43
+ }).on("error", (error) => {
44
+ if (signal.aborted) {
45
+ reject(error);
46
+ return;
47
+ }
48
+ resolve(null);
49
+ });
50
+ });
51
+ }
52
+ function cleanGitValue(value) {
53
+ const cleaned = value?.trim();
54
+ return cleaned ? cleaned : null;
55
+ }
56
+ //#endregion
57
+ export { readLocalGitState };
@@ -5,8 +5,12 @@ import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  //#region src/lib/project/resolution.ts
7
7
  async function resolveProjectTarget(options) {
8
+ const localPin = await readImplicitLocalPin(options, { allowEnvProjectId: true });
8
9
  const projects = await options.listProjects();
9
- const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: true });
10
+ const target = await resolveBoundProjectTarget(options, projects, {
11
+ allowEnvProjectId: true,
12
+ localPin
13
+ });
10
14
  if (target) return target;
11
15
  throw await projectSetupRequiredError({
12
16
  cwd: options.context.runtime.cwd,
@@ -16,8 +20,12 @@ async function resolveProjectTarget(options) {
16
20
  });
17
21
  }
18
22
  async function inspectProjectBinding(options) {
23
+ const localPin = await readImplicitLocalPin(options, { allowEnvProjectId: false });
19
24
  const projects = await options.listProjects();
20
- const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
25
+ const target = await resolveBoundProjectTarget(options, projects, {
26
+ allowEnvProjectId: false,
27
+ localPin
28
+ });
21
29
  if (target) return target;
22
30
  return {
23
31
  workspace: options.workspace,
@@ -73,6 +81,28 @@ function localStateStaleError() {
73
81
  nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
74
82
  });
75
83
  }
84
+ function localProjectWorkspaceMismatchError(options) {
85
+ return new CliError({
86
+ code: "LOCAL_PROJECT_WORKSPACE_MISMATCH",
87
+ domain: "project",
88
+ summary: "Project link uses another workspace",
89
+ why: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but your current CLI session is workspace "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
90
+ fix: "Sign in to the linked workspace, or relink this directory to a project in the current workspace.",
91
+ meta: {
92
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
93
+ pinnedWorkspaceId: options.pinnedWorkspaceId,
94
+ pinnedProjectId: options.pinnedProjectId,
95
+ activeWorkspaceId: options.activeWorkspace.id,
96
+ activeWorkspaceName: options.activeWorkspace.name
97
+ },
98
+ exitCode: 1,
99
+ nextSteps: [
100
+ "prisma-cli auth login",
101
+ "prisma-cli project list",
102
+ "prisma-cli project link <id-or-name>"
103
+ ]
104
+ });
105
+ }
76
106
  async function buildProjectSetupSuggestion(options) {
77
107
  const suggestedName = await inferTargetName(options.cwd, options.signal);
78
108
  const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
@@ -192,10 +222,15 @@ async function resolveBoundProjectTarget(options, projects, settings) {
192
222
  targetNameSource: "env"
193
223
  });
194
224
  }
195
- const localPin = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
225
+ const localPin = settings.localPin;
226
+ if (!localPin) return null;
196
227
  if (localPin.kind === "invalid") throw localStateStaleError();
197
228
  if (localPin.kind === "present") {
198
- if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
229
+ if (localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
230
+ pinnedWorkspaceId: localPin.pin.workspaceId,
231
+ pinnedProjectId: localPin.pin.projectId,
232
+ activeWorkspace: options.workspace
233
+ });
199
234
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
200
235
  if (!project) throw localStateStaleError();
201
236
  return resolvedTarget(options.workspace, project, "local-pin", {
@@ -210,6 +245,16 @@ async function resolveBoundProjectTarget(options, projects, settings) {
210
245
  });
211
246
  return null;
212
247
  }
248
+ async function readImplicitLocalPin(options, settings) {
249
+ if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return null;
250
+ const localPin = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
251
+ if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
252
+ pinnedWorkspaceId: localPin.pin.workspaceId,
253
+ pinnedProjectId: localPin.pin.projectId,
254
+ activeWorkspace: options.workspace
255
+ });
256
+ return localPin;
257
+ }
213
258
  function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
214
259
  return {
215
260
  workspace,