@prisma/cli 3.0.0-dev.41.1 → 3.0.0-dev.42.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.
@@ -11,9 +11,10 @@ import { parseEnvAssignments } from "../lib/app/env-vars.js";
11
11
  import { renderDeployOutputRows } 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";
14
+ import { formatCommandArgument } from "../shell/command-arguments.js";
14
15
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
15
- import { inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
16
- import { bindProjectToDirectory, formatCommandArgument, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
16
+ import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
17
+ import { bindProjectToDirectory, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
17
18
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
18
19
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
19
20
  import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
@@ -1796,24 +1797,41 @@ function deployFailedError(summary, error, nextSteps) {
1796
1797
  function appDeployFailedError(error, progress) {
1797
1798
  const why = error instanceof Error ? error.message : String(error);
1798
1799
  const debug = formatDebugDetails(error);
1799
- if (progress.buildStarted && !progress.buildCompleted) return new CliError({
1800
- code: "BUILD_FAILED",
1801
- domain: "app",
1802
- summary: "Build failed locally.",
1803
- why,
1804
- fix: "Inspect the build output above, fix the error, and redeploy.",
1805
- debug,
1806
- meta: { phase: "build" },
1807
- humanLines: [
1808
- "Build failed locally.",
1809
- "",
1810
- `✗ Built ${why}`,
1811
- "",
1812
- "Fix: Inspect the build output above, fix the error, and redeploy."
1813
- ],
1814
- exitCode: 1,
1815
- nextSteps: []
1816
- });
1800
+ if (progress.buildStarted && !progress.buildCompleted) {
1801
+ const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
1802
+ const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
1803
+ const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
1804
+ const nextActions = standaloneOutputFailure ? [{
1805
+ kind: "edit-file",
1806
+ journey: "deploy-app",
1807
+ label: "Add Next.js standalone output",
1808
+ reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
1809
+ }, {
1810
+ kind: "run-command",
1811
+ journey: "deploy-app",
1812
+ label: "Rerun deploy",
1813
+ command: "prisma-cli app deploy"
1814
+ }] : [];
1815
+ return new CliError({
1816
+ code: "BUILD_FAILED",
1817
+ domain: "app",
1818
+ summary: "Build failed locally.",
1819
+ why,
1820
+ fix,
1821
+ debug,
1822
+ meta: { phase: "build" },
1823
+ humanLines: [
1824
+ "Build failed locally.",
1825
+ "",
1826
+ `✗ Built ${why}`,
1827
+ "",
1828
+ `Fix: ${fix}`
1829
+ ],
1830
+ exitCode: 1,
1831
+ nextSteps,
1832
+ nextActions
1833
+ });
1834
+ }
1817
1835
  if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
1818
1836
  const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
1819
1837
  const recoveryLines = progress.versionId ? ["See what happened", `prisma-cli app logs --deployment ${progress.versionId}`] : ["Fix", "Retry the command, or rerun with --trace for more detailed diagnostics."];
@@ -1896,9 +1914,17 @@ function projectSetupRequiredError(projects, suggestedName) {
1896
1914
  "prisma-cli project list",
1897
1915
  "prisma-cli app deploy --project <id-or-name>",
1898
1916
  createCommand
1899
- ]
1917
+ ],
1918
+ nextActions: buildProjectSetupNextActions({
1919
+ commandName: "app deploy",
1920
+ createCommand,
1921
+ reason: "This directory is not linked to a Prisma Project. Ask the user which Project to use before deploying; package and directory names are setup suggestions only."
1922
+ })
1900
1923
  });
1901
1924
  }
1925
+ function isNextStandaloneOutputFailure(message) {
1926
+ return /next\.?js/i.test(message) && /standalone output/i.test(message);
1927
+ }
1902
1928
  function noDeploymentsError(summary, why) {
1903
1929
  return new CliError({
1904
1930
  code: "NO_DEPLOYMENTS",
@@ -2,7 +2,8 @@ import { CliError, authRequiredError, featureUnavailableError, usageError, works
2
2
  import { renderSummaryLine } from "../shell/ui.js";
3
3
  import { canPrompt } from "../shell/runtime.js";
4
4
  import { requireComputeAuth } from "../lib/auth/guard.js";
5
- import { inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
5
+ import { readLocalResolutionPin } from "../lib/project/local-pin.js";
6
+ import { buildProjectSetupNextActions, inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
6
7
  import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
7
8
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
8
9
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
@@ -16,6 +17,12 @@ const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
16
17
  function isRealMode(context) {
17
18
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
18
19
  }
20
+ async function readProjectListLocalBinding(cwd, workspace, projects) {
21
+ const pin = await readLocalResolutionPin(cwd);
22
+ if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
23
+ if (pin.kind === "invalid") return { status: "invalid" };
24
+ return { status: "not-linked" };
25
+ }
19
26
  async function runProjectList(context) {
20
27
  const authState = await requireAuthenticatedAuthState(context);
21
28
  const workspace = authState.workspace;
@@ -23,31 +30,52 @@ async function runProjectList(context) {
23
30
  if (isRealMode(context)) {
24
31
  const client = await requireComputeAuth(context.runtime.env);
25
32
  if (!client) throw authRequiredError();
33
+ const projects = sortProjects(await listRealWorkspaceProjects(client, workspace));
34
+ const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects);
35
+ const nextActions = buildProjectListNextActions(localBinding);
26
36
  return {
27
37
  command: "project.list",
28
38
  result: {
29
39
  workspace,
30
- projects: sortProjects(await listRealWorkspaceProjects(client, workspace)).map(toProjectSummary)
40
+ projects: projects.map(toProjectSummary),
41
+ localBinding
31
42
  },
32
43
  warnings: [],
33
- nextSteps: []
44
+ nextSteps: [],
45
+ nextActions
34
46
  };
35
47
  }
48
+ const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
49
+ const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects);
50
+ const nextActions = buildProjectListNextActions(localBinding);
36
51
  return {
37
52
  command: "project.list",
38
- result: await createProjectUseCases(createCliUseCaseGateways(context)).list(authState),
53
+ result: {
54
+ ...result,
55
+ localBinding
56
+ },
39
57
  warnings: [],
40
- nextSteps: []
58
+ nextSteps: [],
59
+ nextActions
41
60
  };
42
61
  }
62
+ function buildProjectListNextActions(localBinding) {
63
+ return localBinding?.status === "linked" ? [] : buildProjectSetupNextActions({ reason: localBinding?.status === "invalid" ? "This directory has an invalid local Project binding. Ask the user which Prisma Project to link before running Project-scoped commands." : "This directory is not linked to a Prisma Project. Project list shows available Projects, but none is selected for this directory." });
64
+ }
43
65
  async function runProjectShow(context, explicitProject) {
44
66
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
45
67
  if (!workspace) throw workspaceRequiredError();
68
+ const result = isRealMode(context) ? await resolveProjectShowInRealMode(context, workspace, explicitProject) : await resolveProjectShowInFixtureMode(context, workspace, explicitProject);
46
69
  return {
47
70
  command: "project.show",
48
- result: isRealMode(context) ? await resolveProjectShowInRealMode(context, workspace, explicitProject) : await resolveProjectShowInFixtureMode(context, workspace, explicitProject),
71
+ result,
49
72
  warnings: [],
50
- nextSteps: []
73
+ nextSteps: [],
74
+ nextActions: result.project === null ? buildProjectSetupNextActions({
75
+ commandName: "project show",
76
+ suggestedProjectName: result.suggestedProjectName,
77
+ reason: "This directory is not linked to a Prisma Project. Package and directory names can suggest setup defaults, but they do not select a Project."
78
+ }) : []
51
79
  };
52
80
  }
53
81
  async function runProjectCreate(context, projectName) {
@@ -1,4 +1,5 @@
1
1
  import { CliError } from "../../shell/errors.js";
2
+ import { formatCommandArgument } from "../../shell/command-arguments.js";
2
3
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
3
4
  import { readFile } from "node:fs/promises";
4
5
  import path from "node:path";
@@ -20,6 +21,7 @@ async function inspectProjectBinding(options) {
20
21
  return {
21
22
  workspace: options.workspace,
22
23
  project: null,
24
+ localBinding: { status: "not-linked" },
23
25
  resolution: { projectSource: "unbound" },
24
26
  ...await buildProjectSetupSuggestion({
25
27
  cwd: options.context.runtime.cwd,
@@ -89,8 +91,45 @@ async function projectSetupRequiredError(options) {
89
91
  fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
90
92
  meta: { ...suggestion },
91
93
  exitCode: 1,
92
- nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands]
94
+ nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
95
+ nextActions: buildProjectSetupNextActions({
96
+ commandName: options.commandName,
97
+ suggestedProjectName: suggestion.suggestedProjectName
98
+ })
99
+ });
100
+ }
101
+ function buildProjectSetupNextActions(options = {}) {
102
+ const recoveryCommands = buildProjectRecoveryCommands(options.commandName);
103
+ const linkCommand = recoveryCommands[0] ?? "prisma-cli project link <id-or-name>";
104
+ const retryCommand = recoveryCommands[1];
105
+ const actions = [{
106
+ kind: "user-choice",
107
+ journey: "project-setup",
108
+ label: "Ask the user which Prisma Project this directory should use",
109
+ commands: ["prisma-cli project list", ...recoveryCommands],
110
+ reason: options.reason ?? "This directory is not linked to a Prisma Project. Package and directory names are suggestions only, not a safe Project selection."
111
+ }, {
112
+ kind: "run-command",
113
+ journey: "project-setup",
114
+ label: "Link the chosen Project",
115
+ command: linkCommand,
116
+ reason: "Linking writes the durable local Project binding for this directory."
117
+ }];
118
+ const createCommand = options.createCommand ?? (options.suggestedProjectName ? `prisma-cli project create ${formatCommandArgument(options.suggestedProjectName)}` : void 0);
119
+ if (createCommand) actions.push({
120
+ kind: "run-command",
121
+ journey: "project-setup",
122
+ label: "Create and link a new Project",
123
+ command: createCommand,
124
+ reason: "Use this when the user wants a new Prisma Project instead of an existing one."
125
+ });
126
+ if (options.commandName) actions.push({
127
+ kind: "run-command",
128
+ journey: "recover",
129
+ label: "Retry with an explicit Project",
130
+ command: retryCommand ?? `prisma-cli ${options.commandName} --project <id-or-name>`
93
131
  });
132
+ return actions;
94
133
  }
95
134
  async function readPackageName(cwd) {
96
135
  try {
@@ -187,4 +226,4 @@ function toProjectSummary(project) {
187
226
  };
188
227
  }
189
228
  //#endregion
190
- export { inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
229
+ export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
@@ -1,4 +1,5 @@
1
1
  import { CliError, usageError } from "../../shell/errors.js";
2
+ import "../../shell/command-arguments.js";
2
3
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
3
4
  import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
4
5
  //#region src/lib/project/setup.ts
@@ -60,9 +61,6 @@ function projectCreateFailedError(error, projectName, workspace, options) {
60
61
  nextSteps: options.nextSteps
61
62
  });
62
63
  }
63
- function formatCommandArgument(value) {
64
- return /^[A-Za-z0-9._/-]+$/.test(value) ? value : JSON.stringify(value);
65
- }
66
64
  function formatSetupDirectory(cwd) {
67
65
  const basename = cwd.split(/[\\/]/).filter(Boolean).pop();
68
66
  return basename ? `./${basename}` : ".";
@@ -83,4 +81,4 @@ function formatDebugDetails(error) {
83
81
  return typeof error === "string" ? error : null;
84
82
  }
85
83
  //#endregion
86
- export { bindProjectToDirectory, formatCommandArgument, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary };
84
+ export { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary };
@@ -1,8 +1,9 @@
1
- import { renderSummaryLine } from "../shell/ui.js";
1
+ import { renderNextSteps, renderSummaryLine } from "../shell/ui.js";
2
+ import { formatCommandArgument } from "../shell/command-arguments.js";
2
3
  import { renderList, renderMutate, renderShow, serializeList } from "../output/patterns.js";
3
4
  //#region src/presenters/project.ts
4
5
  function renderProjectList(context, descriptor, result) {
5
- return renderList({
6
+ const lines = renderList({
6
7
  title: "Listing projects for the authenticated workspace.",
7
8
  descriptor,
8
9
  parentContext: {
@@ -17,46 +18,40 @@ function renderProjectList(context, descriptor, result) {
17
18
  })),
18
19
  emptyMessage: "No projects found."
19
20
  }, context.ui);
21
+ if (result.localBinding?.status === "not-linked" || result.localBinding?.status === "invalid") lines.push(...renderNextSteps(["Link the chosen Project: prisma-cli project link <id-or-name>"]));
22
+ return lines;
20
23
  }
21
24
  function serializeProjectList(result) {
22
- return serializeList({
23
- context: { workspace: result.workspace.name },
24
- items: result.projects.map((project) => ({
25
- noun: "project",
26
- label: project.name,
27
- id: project.id,
28
- status: null
29
- }))
30
- });
25
+ return {
26
+ ...serializeList({
27
+ context: { workspace: result.workspace.name },
28
+ items: result.projects.map((project) => ({
29
+ noun: "project",
30
+ label: project.name,
31
+ id: project.id,
32
+ status: null
33
+ }))
34
+ }),
35
+ localBinding: result.localBinding ?? null
36
+ };
31
37
  }
32
38
  function renderProjectShow(context, descriptor, result) {
33
- if (result.project === null) return renderShow({
34
- title: "No Project linked to this directory.",
35
- descriptor,
36
- fields: [
37
- {
39
+ if (result.project === null) {
40
+ const lines = renderShow({
41
+ title: "This directory is not linked to a Prisma Project.",
42
+ descriptor,
43
+ fields: [{
38
44
  key: "workspace",
39
45
  value: result.workspace.name
40
- },
41
- {
46
+ }, {
42
47
  key: "project",
43
- value: "unbound",
48
+ value: "Not linked",
44
49
  tone: "warning"
45
- },
46
- {
47
- key: "suggested",
48
- value: `${result.suggestedProjectName} (${formatSuggestionSource(result.suggestedProjectNameSource)})`
49
- },
50
- {
51
- key: "match",
52
- value: formatCandidateList(result.candidates)
53
- },
54
- {
55
- key: "next",
56
- value: result.recoveryCommands[0] ?? "prisma-cli project link <id-or-name>"
57
- }
58
- ]
59
- }, context.ui);
50
+ }]
51
+ }, context.ui);
52
+ lines.push(...renderNextSteps(["Link an existing Project: prisma-cli project link <id-or-name>", `Create a new Project: prisma-cli project create ${formatCommandArgument(result.suggestedProjectName)}`]));
53
+ return lines;
54
+ }
60
55
  return renderShow({
61
56
  title: "Showing this directory's Project binding.",
62
57
  descriptor,
@@ -149,16 +144,6 @@ function formatProjectSource(source) {
149
144
  case "unbound": return "unbound";
150
145
  }
151
146
  }
152
- function formatSuggestionSource(source) {
153
- switch (source) {
154
- case "package-name": return "package name";
155
- case "directory-name": return "directory name";
156
- }
157
- }
158
- function formatCandidateList(candidates) {
159
- if (candidates.length === 0) return "none";
160
- return candidates.map((project) => project.name).join(", ");
161
- }
162
147
  function formatGitConnectionDetail(status) {
163
148
  switch (status) {
164
149
  case "active": return "GitHub branch automation is active for this project.";
@@ -0,0 +1,6 @@
1
+ //#region src/shell/command-arguments.ts
2
+ function formatCommandArgument(value) {
3
+ return /^[A-Za-z0-9._/-]+$/.test(value) && !value.startsWith("-") ? value : `'${value.replace(/'/g, "'\\''")}'`;
4
+ }
5
+ //#endregion
6
+ export { formatCommandArgument };
@@ -47,7 +47,8 @@ async function runStreamingCommand(runtime, commandName, options, handler) {
47
47
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
48
48
  result: null,
49
49
  warnings: [],
50
- nextSteps: []
50
+ nextSteps: [],
51
+ nextActions: []
51
52
  });
52
53
  } catch (error) {
53
54
  const cliError = toCliError(error);
@@ -58,7 +59,8 @@ async function runStreamingCommand(runtime, commandName, options, handler) {
58
59
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
59
60
  error: cliErrorToJson(cliError),
60
61
  warnings: [],
61
- nextSteps: cliError.nextSteps
62
+ nextSteps: cliError.nextSteps,
63
+ nextActions: cliError.nextActions
62
64
  });
63
65
  else writeHumanError(context.output, context.ui, cliError, { trace: flags.trace });
64
66
  process.exitCode = cliError.exitCode;
@@ -12,6 +12,7 @@ var CliError = class extends Error {
12
12
  docsUrl;
13
13
  exitCode;
14
14
  nextSteps;
15
+ nextActions;
15
16
  humanLines;
16
17
  constructor(options) {
17
18
  super(options.summary);
@@ -28,6 +29,7 @@ var CliError = class extends Error {
28
29
  this.docsUrl = options.docsUrl ?? null;
29
30
  this.exitCode = options.exitCode ?? 1;
30
31
  this.nextSteps = options.nextSteps ?? [];
32
+ this.nextActions = options.nextActions ?? [];
31
33
  this.humanLines = options.humanLines && options.humanLines.length > 0 ? [...options.humanLines] : null;
32
34
  }
33
35
  };
@@ -3,6 +3,7 @@ import { renderNextSteps, renderSummaryLine } from "./ui.js";
3
3
  function writeJsonSuccess(output, success) {
4
4
  output.stdout.write(`${JSON.stringify({
5
5
  ok: true,
6
+ nextActions: [],
6
7
  ...success
7
8
  }, null, 2)}\n`);
8
9
  }
@@ -28,7 +29,8 @@ function writeJsonError(output, command, error) {
28
29
  command,
29
30
  error: cliErrorToJson(error),
30
31
  warnings: [],
31
- nextSteps: error.nextSteps
32
+ nextSteps: error.nextSteps,
33
+ nextActions: error.nextActions
32
34
  }, null, 2)}\n`);
33
35
  }
34
36
  function writeHumanLines(output, lines) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "3.0.0-dev.41.1",
3
+ "version": "3.0.0-dev.42.1",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {