@prisma/cli 3.0.0-dev.40.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.
@@ -1,7 +1,6 @@
1
1
  import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
2
  import { requireComputeAuth } from "../lib/auth/guard.js";
3
3
  import { resolveProjectTarget } from "../lib/project/resolution.js";
4
- import { createSelectPromptPort } from "./select-prompt-port.js";
5
4
  import { requireAuthenticatedAuthState } from "./auth.js";
6
5
  import { listRealWorkspaceProjects } from "./project.js";
7
6
  import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
@@ -19,7 +18,7 @@ async function runEnvAdd(context, rawAssignment, flags) {
19
18
  command: "add"
20
19
  });
21
20
  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");
22
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
21
+ const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env add");
23
22
  const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: true });
24
23
  if (await findVariableByNaturalKey(client, projectId, key, resolved)) throw new CliError({
25
24
  code: "ENV_VARIABLE_ALREADY_EXISTS",
@@ -70,7 +69,7 @@ async function runEnvUpdate(context, rawAssignment, flags) {
70
69
  command: "update"
71
70
  });
72
71
  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");
73
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
72
+ const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env update");
74
73
  const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: false });
75
74
  const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
76
75
  if (!existing) throw new CliError({
@@ -103,7 +102,7 @@ async function runEnvList(context, flags) {
103
102
  requireExplicit: false,
104
103
  command: "list"
105
104
  }) ?? defaultRoleScope();
106
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
105
+ const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
107
106
  const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: false });
108
107
  const variables = await listVariables(client, projectId, resolved);
109
108
  return {
@@ -124,7 +123,7 @@ async function runEnvRemove(context, key, flags) {
124
123
  command: "remove"
125
124
  });
126
125
  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");
127
- const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
126
+ const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env remove");
128
127
  const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: false });
129
128
  const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
130
129
  if (!existing) throw new CliError({
@@ -149,7 +148,7 @@ async function runEnvRemove(context, key, flags) {
149
148
  nextSteps: []
150
149
  };
151
150
  }
152
- async function requireClientAndProject(context, explicitProject) {
151
+ async function requireClientAndProject(context, explicitProject, commandName) {
153
152
  const authState = await requireAuthenticatedAuthState(context);
154
153
  const client = await requireComputeAuth(context.runtime.env);
155
154
  if (!client) throw authRequiredError(["prisma-cli auth login"]);
@@ -161,8 +160,7 @@ async function requireClientAndProject(context, explicitProject) {
161
160
  workspace: authState.workspace,
162
161
  explicitProject,
163
162
  listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
164
- prompt: createSelectPromptPort(context),
165
- remember: true
163
+ commandName
166
164
  })).project.id
167
165
  };
168
166
  }
@@ -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 { inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
14
+ import { formatCommandArgument } from "../shell/command-arguments.js";
15
15
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.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";
@@ -203,7 +204,7 @@ async function runAppDeploy(context, appName, options) {
203
204
  }
204
205
  async function runAppListDeploys(context, appName, projectRef) {
205
206
  ensurePreviewAppMode(context);
206
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
207
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app list-deploys" });
207
208
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
208
209
  if (!selectedApp) return {
209
210
  command: "app.list-deploys",
@@ -240,7 +241,7 @@ async function runAppListDeploys(context, appName, projectRef) {
240
241
  }
241
242
  async function runAppShow(context, appName, projectRef) {
242
243
  ensurePreviewAppMode(context);
243
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
244
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app show" });
244
245
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
245
246
  if (!selectedApp) return {
246
247
  command: "app.show",
@@ -316,7 +317,7 @@ async function runAppShowDeploy(context, deploymentId) {
316
317
  }
317
318
  async function runAppOpen(context, appName, projectRef) {
318
319
  ensurePreviewAppMode(context);
319
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
320
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
320
321
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
321
322
  if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
322
323
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
@@ -350,7 +351,7 @@ async function runAppOpen(context, appName, projectRef) {
350
351
  }
351
352
  async function runAppDomainAdd(context, hostname, options) {
352
353
  const normalizedHostname = normalizeDomainHostname(hostname);
353
- const target = await resolveAppDomainTarget(context, options);
354
+ const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
354
355
  const added = await target.provider.addDomain({
355
356
  appId: target.app.id,
356
357
  hostname: normalizedHostname
@@ -370,7 +371,7 @@ async function runAppDomainAdd(context, hostname, options) {
370
371
  }
371
372
  async function runAppDomainShow(context, hostname, options) {
372
373
  const normalizedHostname = normalizeDomainHostname(hostname);
373
- const target = await resolveAppDomainTarget(context, options);
374
+ const target = await resolveAppDomainTarget(context, options, `app domain show ${normalizedHostname}`);
374
375
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show");
375
376
  const detail = await target.provider.showDomain(domain.id).catch((error) => {
376
377
  throw domainCommandError("show", error, normalizedHostname);
@@ -387,7 +388,7 @@ async function runAppDomainShow(context, hostname, options) {
387
388
  }
388
389
  async function runAppDomainRemove(context, hostname, options) {
389
390
  const normalizedHostname = normalizeDomainHostname(hostname);
390
- const target = await resolveAppDomainTarget(context, options);
391
+ const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
391
392
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
392
393
  await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
393
394
  await target.provider.removeDomain(domain.id).catch((error) => {
@@ -406,7 +407,7 @@ async function runAppDomainRemove(context, hostname, options) {
406
407
  }
407
408
  async function runAppDomainRetry(context, hostname, options) {
408
409
  const normalizedHostname = normalizeDomainHostname(hostname);
409
- const target = await resolveAppDomainTarget(context, options);
410
+ const target = await resolveAppDomainTarget(context, options, `app domain retry ${normalizedHostname}`);
410
411
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry");
411
412
  const retried = await target.provider.retryDomain(domain.id).catch((error) => {
412
413
  throw domainCommandError("retry", error, normalizedHostname);
@@ -424,7 +425,7 @@ async function runAppDomainRetry(context, hostname, options) {
424
425
  async function runAppDomainWait(context, hostname, options) {
425
426
  const normalizedHostname = normalizeDomainHostname(hostname);
426
427
  const timeoutMs = parseDomainWaitTimeout(options?.timeout);
427
- const target = await resolveAppDomainTarget(context, options);
428
+ const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
428
429
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
429
430
  if (!context.flags.json && !context.flags.quiet) context.output.stderr.write([
430
431
  `app domain wait -> Waiting for ${normalizedHostname} to become active.`,
@@ -476,7 +477,7 @@ async function runAppDomainWait(context, hostname, options) {
476
477
  }
477
478
  async function runAppLogs(context, appName, deploymentId, projectRef) {
478
479
  ensurePreviewAppMode(context);
479
- const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef);
480
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app logs" });
480
481
  const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
481
482
  if (!context.flags.json && !context.flags.quiet) {
482
483
  const lines = renderCommandHeader(context.ui, {
@@ -600,7 +601,7 @@ function writeLogRecord(context, record) {
600
601
  }
601
602
  async function runAppPromote(context, deploymentId, appName, projectRef) {
602
603
  ensurePreviewAppMode(context);
603
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
604
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
604
605
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
605
606
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
606
607
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -640,7 +641,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
640
641
  }
641
642
  async function runAppRollback(context, appName, deploymentId, projectRef) {
642
643
  ensurePreviewAppMode(context);
643
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
644
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
644
645
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
645
646
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
646
647
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -682,7 +683,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
682
683
  }
683
684
  async function runAppRemove(context, appName, projectRef) {
684
685
  ensurePreviewAppMode(context);
685
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
686
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
686
687
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
687
688
  await confirmAppRemoval(context, selectedApp);
688
689
  const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
@@ -703,7 +704,7 @@ async function runAppRemove(context, appName, projectRef) {
703
704
  nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
704
705
  };
705
706
  }
706
- async function resolveAppDomainTarget(context, options) {
707
+ async function resolveAppDomainTarget(context, options, commandName = "app domain") {
707
708
  ensurePreviewAppMode(context);
708
709
  const branch = resolveDomainBranch(options?.branchName);
709
710
  if (toBranchKind(branch.name) !== "production") throw new CliError({
@@ -717,13 +718,10 @@ async function resolveAppDomainTarget(context, options) {
717
718
  });
718
719
  const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
719
720
  const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
720
- const skipLocalPin = Boolean(envProjectId || options?.projectRef);
721
- const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
722
- if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
723
- const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
721
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
724
722
  branch,
725
- envProjectId,
726
- localPin
723
+ commandName,
724
+ envProjectId
727
725
  });
728
726
  const selectedApp = await resolveDomainAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
729
727
  explicitAppName: options?.appName,
@@ -1218,9 +1216,9 @@ async function listApps(context, provider, projectId, branchName) {
1218
1216
  domain: "project",
1219
1217
  summary: "Project not found",
1220
1218
  why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
1221
- fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect resolution for this directory.",
1219
+ fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect this directory's binding.",
1222
1220
  exitCode: 1,
1223
- nextSteps: ["prisma-cli project show", "prisma-cli app deploy --project <id-or-name>"]
1221
+ nextSteps: ["prisma-cli project show", "prisma-cli project link <id-or-name>"]
1224
1222
  });
1225
1223
  throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
1226
1224
  });
@@ -1280,8 +1278,9 @@ async function resolveProjectContext(context, client, explicitProject, options)
1280
1278
  context,
1281
1279
  workspace: authState.workspace,
1282
1280
  explicitProject,
1281
+ envProjectId: options?.envProjectId,
1283
1282
  listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
1284
- remember: true
1283
+ commandName: options?.commandName
1285
1284
  });
1286
1285
  const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
1287
1286
  return {
@@ -1798,24 +1797,41 @@ function deployFailedError(summary, error, nextSteps) {
1798
1797
  function appDeployFailedError(error, progress) {
1799
1798
  const why = error instanceof Error ? error.message : String(error);
1800
1799
  const debug = formatDebugDetails(error);
1801
- if (progress.buildStarted && !progress.buildCompleted) return new CliError({
1802
- code: "BUILD_FAILED",
1803
- domain: "app",
1804
- summary: "Build failed locally.",
1805
- why,
1806
- fix: "Inspect the build output above, fix the error, and redeploy.",
1807
- debug,
1808
- meta: { phase: "build" },
1809
- humanLines: [
1810
- "Build failed locally.",
1811
- "",
1812
- `✗ Built ${why}`,
1813
- "",
1814
- "Fix: Inspect the build output above, fix the error, and redeploy."
1815
- ],
1816
- exitCode: 1,
1817
- nextSteps: []
1818
- });
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
+ }
1819
1835
  if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
1820
1836
  const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
1821
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."];
@@ -1862,10 +1878,14 @@ function localResolutionPinStaleError() {
1862
1878
  domain: "project",
1863
1879
  summary: "Local project binding is stale",
1864
1880
  why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
1865
- fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} and re-run to re-bootstrap.`,
1881
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
1866
1882
  meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
1867
1883
  exitCode: 1,
1868
- nextSteps: ["prisma-cli app deploy"]
1884
+ nextSteps: [
1885
+ "prisma-cli project list",
1886
+ "prisma-cli project link <id-or-name>",
1887
+ "prisma-cli app deploy --project <id-or-name>"
1888
+ ]
1869
1889
  });
1870
1890
  }
1871
1891
  function readDeployEnvOverride(context, name) {
@@ -1894,9 +1914,17 @@ function projectSetupRequiredError(projects, suggestedName) {
1894
1914
  "prisma-cli project list",
1895
1915
  "prisma-cli app deploy --project <id-or-name>",
1896
1916
  createCommand
1897
- ]
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
+ })
1898
1923
  });
1899
1924
  }
1925
+ function isNextStandaloneOutputFailure(message) {
1926
+ return /next\.?js/i.test(message) && /standalone output/i.test(message);
1927
+ }
1900
1928
  function noDeploymentsError(summary, why) {
1901
1929
  return new CliError({
1902
1930
  code: "NO_DEPLOYMENTS",
@@ -2,8 +2,9 @@ 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 { resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
6
- import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup } from "../lib/project/setup.js";
5
+ import { readLocalResolutionPin } from "../lib/project/local-pin.js";
6
+ import { buildProjectSetupNextActions, inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
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";
9
10
  import { requireAuthenticatedAuthState } from "./auth.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) {
@@ -94,7 +122,7 @@ async function runGitConnect(context, gitUrl, options = {}) {
94
122
  if (isRealMode(context)) {
95
123
  const client = await requireComputeAuth(context.runtime.env);
96
124
  if (!client) throw authRequiredError();
97
- const target = await resolveProjectShowInRealMode(context, workspace, options.project);
125
+ const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
98
126
  const repository = await resolveRepositoryForConnect(context, gitUrl);
99
127
  const api = client;
100
128
  const existing = await readFirstSourceRepository(api, target.project.id);
@@ -129,7 +157,7 @@ async function runGitConnect(context, gitUrl, options = {}) {
129
157
  nextSteps: []
130
158
  };
131
159
  }
132
- const target = await resolveProjectShowInFixtureMode(context, workspace, options.project);
160
+ const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git connect");
133
161
  const repository = await resolveRepositoryForConnect(context, gitUrl);
134
162
  const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
135
163
  if (existingConnection) {
@@ -162,7 +190,7 @@ async function runGitDisconnect(context, options = {}) {
162
190
  if (isRealMode(context)) {
163
191
  const client = await requireComputeAuth(context.runtime.env);
164
192
  if (!client) throw authRequiredError();
165
- const target = await resolveProjectShowInRealMode(context, workspace, options.project);
193
+ const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
166
194
  const api = client;
167
195
  const existing = await readFirstSourceRepository(api, target.project.id);
168
196
  if (!existing) throw repoNotConnectedError();
@@ -178,7 +206,7 @@ async function runGitDisconnect(context, options = {}) {
178
206
  nextSteps: []
179
207
  };
180
208
  }
181
- const target = await resolveProjectShowInFixtureMode(context, workspace, options.project);
209
+ const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git disconnect");
182
210
  const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
183
211
  if (!existingConnection) throw repoNotConnectedError();
184
212
  await context.stateStore.clearRepositoryConnection(target.project.id);
@@ -193,6 +221,17 @@ async function runGitDisconnect(context, options = {}) {
193
221
  };
194
222
  }
195
223
  async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
224
+ const client = await requireComputeAuth(context.runtime.env);
225
+ if (!client) throw authRequiredError();
226
+ return inspectProjectBinding({
227
+ context,
228
+ workspace,
229
+ explicitProject,
230
+ listProjects: () => listRealWorkspaceProjects(client, workspace),
231
+ commandName: "project show"
232
+ });
233
+ }
234
+ async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
196
235
  const client = await requireComputeAuth(context.runtime.env);
197
236
  if (!client) throw authRequiredError();
198
237
  return resolveProjectTarget({
@@ -200,7 +239,7 @@ async function resolveProjectShowInRealMode(context, workspace, explicitProject)
200
239
  workspace,
201
240
  explicitProject,
202
241
  listProjects: () => listRealWorkspaceProjects(client, workspace),
203
- remember: false
242
+ commandName
204
243
  });
205
244
  }
206
245
  async function listRealProjectsForLink(context, workspace) {
@@ -209,12 +248,21 @@ async function listRealProjectsForLink(context, workspace) {
209
248
  return listRealWorkspaceProjects(client, workspace);
210
249
  }
211
250
  async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
251
+ return inspectProjectBinding({
252
+ context,
253
+ workspace,
254
+ explicitProject,
255
+ listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
256
+ commandName: "project show"
257
+ });
258
+ }
259
+ async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
212
260
  return resolveProjectTarget({
213
261
  context,
214
262
  workspace,
215
263
  explicitProject,
216
264
  listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
217
- remember: false
265
+ commandName
218
266
  });
219
267
  }
220
268
  async function listRealWorkspaceProjects(client, workspace) {
@@ -539,11 +587,5 @@ function repoConnectionFixForStatus(status) {
539
587
  if (status === 422) return "Make sure the GitHub App installation has access to this repository.";
540
588
  return "Re-run with --trace for the underlying API response details.";
541
589
  }
542
- function toProjectSummary(project) {
543
- return {
544
- id: project.id,
545
- name: project.name
546
- };
547
- }
548
590
  //#endregion
549
591
  export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
@@ -1,73 +1,33 @@
1
1
  import { CliError } from "../../shell/errors.js";
2
- import { canPrompt } from "../../shell/runtime.js";
2
+ import { formatCommandArgument } from "../../shell/command-arguments.js";
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";
5
6
  //#region src/lib/project/resolution.ts
6
7
  async function resolveProjectTarget(options) {
7
8
  const projects = await options.listProjects();
8
- const inferredName = await inferTargetName(options.context.runtime.cwd);
9
- if (options.explicitProject) return rememberIfRequested(options, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
10
- targetName: options.explicitProject,
11
- targetNameSource: "explicit"
9
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: true });
10
+ if (target) return target;
11
+ throw await projectSetupRequiredError({
12
+ cwd: options.context.runtime.cwd,
13
+ projects,
14
+ commandName: options.commandName
12
15
  });
13
- const platformMapping = await resolveDurablePlatformMapping();
14
- if (platformMapping) return rememberIfRequested(options, platformMapping, "platform-mapping");
15
- let staleRemembered = false;
16
- if (!options.allowCreate) {
17
- const rememberedResult = await resolveRememberedProject(options, projects);
18
- if (rememberedResult.target) return rememberedResult.target;
19
- staleRemembered = rememberedResult.stale;
20
- }
21
- const packageName = inferredName.source === "package-name" ? inferredName.name : null;
22
- if (packageName) {
23
- const matches = projects.filter((project) => projectMatchesPackageName(project, packageName));
24
- if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name", {
25
- targetName: packageName,
26
- targetNameSource: "package-name"
27
- });
28
- if (matches.length > 1) return resolveAmbiguousProject(options, matches, packageName, "package-name");
29
- }
30
- if (options.allowCreate && options.createProject) {
31
- if (inferredName.name) {
32
- const existing = projects.filter((project) => projectMatchesPackageName(project, inferredName.name));
33
- if (existing.length === 1) return rememberIfRequested(options, existing[0], inferredName.source, {
34
- targetName: inferredName.name,
35
- targetNameSource: inferredName.source
36
- });
37
- if (existing.length > 1) return resolveAmbiguousProject(options, existing, inferredName.name, inferredName.source);
38
- return rememberIfRequested(options, await options.createProject(inferredName.name), "created", {
39
- targetName: inferredName.name,
40
- targetNameSource: inferredName.source
41
- });
42
- }
43
- }
44
- if (options.prompt && canPrompt(options.context) && projects.length > 0) return rememberIfRequested(options, await options.prompt.select({
45
- message: "Select a project",
46
- choices: sortProjects(projects).map((project) => ({
47
- label: `${project.name} (${project.id})`,
48
- value: project
49
- }))
50
- }), "prompt");
51
- if (staleRemembered && projects.length > 1) throw localStateStaleError();
52
- throw projectUnresolvedError();
53
- }
54
- async function resolveRememberedProject(options, projects) {
55
- const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
56
- if (!remembered) return {
57
- target: null,
58
- stale: false
59
- };
60
- const matched = projects.find((project) => project.id === remembered.id);
61
- if (!matched) return {
62
- target: null,
63
- stale: true
64
- };
16
+ }
17
+ async function inspectProjectBinding(options) {
18
+ const projects = await options.listProjects();
19
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
20
+ if (target) return target;
65
21
  return {
66
- target: await rememberIfRequested(options, matched, "remembered-local", {
67
- targetName: remembered.name,
68
- targetNameSource: "remembered-local"
69
- }),
70
- stale: false
22
+ workspace: options.workspace,
23
+ project: null,
24
+ localBinding: { status: "not-linked" },
25
+ resolution: { projectSource: "unbound" },
26
+ ...await buildProjectSetupSuggestion({
27
+ cwd: options.context.runtime.cwd,
28
+ projects,
29
+ commandName: options.commandName ?? "project show"
30
+ })
71
31
  };
72
32
  }
73
33
  function projectNotFoundError(projectRef, workspace) {
@@ -99,27 +59,77 @@ function projectAmbiguousError(projectRef, matches) {
99
59
  nextSteps
100
60
  });
101
61
  }
102
- function projectUnresolvedError() {
62
+ function localStateStaleError() {
103
63
  return new CliError({
104
- code: "PROJECT_UNRESOLVED",
64
+ code: "LOCAL_STATE_STALE",
105
65
  domain: "project",
106
- summary: "No project is resolved for this directory",
107
- why: "No project could be resolved from explicit input, platform mappings, remembered local context, or package metadata.",
108
- fix: "Pass --project <id-or-name> on the command that needs a project, or add a package.json name that matches an accessible project.",
66
+ summary: "Local project binding is stale",
67
+ why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
68
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
69
+ meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
109
70
  exitCode: 1,
110
- nextSteps: ["prisma-cli project list", "prisma-cli project show --project <id-or-name>"]
71
+ nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
111
72
  });
112
73
  }
113
- function localStateStaleError() {
74
+ async function buildProjectSetupSuggestion(options) {
75
+ const suggestedName = await inferTargetName(options.cwd);
76
+ const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
77
+ return {
78
+ suggestedProjectName: suggestedName.name,
79
+ suggestedProjectNameSource: suggestedName.source,
80
+ candidates,
81
+ recoveryCommands: buildProjectRecoveryCommands(options.commandName)
82
+ };
83
+ }
84
+ async function projectSetupRequiredError(options) {
85
+ const suggestion = await buildProjectSetupSuggestion(options);
114
86
  return new CliError({
115
- code: "LOCAL_STATE_STALE",
87
+ code: "PROJECT_SETUP_REQUIRED",
116
88
  domain: "project",
117
- summary: "Remembered project context is stale",
118
- why: "The remembered project is no longer available in the selected workspace, and automatic resolution would be ambiguous.",
119
- fix: "Pass --project <id-or-name> to choose the project explicitly.",
89
+ summary: "Choose a Project before running this command",
90
+ why: `This directory is not linked to a Prisma Project, and ${options.commandName ? `prisma-cli ${options.commandName}` : "this command"} will not choose one from package or directory names.`,
91
+ fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
92
+ meta: { ...suggestion },
120
93
  exitCode: 1,
121
- nextSteps: ["prisma-cli project list"]
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."
122
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>`
131
+ });
132
+ return actions;
123
133
  }
124
134
  async function readPackageName(cwd) {
125
135
  try {
@@ -157,33 +167,46 @@ function resolveExplicitProject(projectRef, projects, workspace) {
157
167
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
158
168
  throw projectNotFoundError(projectRef, workspace);
159
169
  }
160
- function resolveAmbiguousProject(options, matches, projectRef, targetNameSource) {
161
- if (options.prompt && canPrompt(options.context)) return options.prompt.select({
162
- message: "Select a project",
163
- choices: sortProjects(matches).map((project) => ({
164
- label: `${project.name} (${project.id})`,
165
- value: project
166
- }))
167
- }).then((selected) => rememberIfRequested(options, selected, "prompt", {
168
- targetName: projectRef,
169
- targetNameSource
170
- }));
171
- throw projectAmbiguousError(projectRef, matches);
172
- }
173
- function projectMatchesPackageName(project, packageName) {
174
- return project.id === packageName || project.name === packageName || project.slug === packageName;
170
+ function projectMatchesSuggestedName(project, suggestedName) {
171
+ return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
175
172
  }
176
173
  async function resolveDurablePlatformMapping() {
177
174
  return null;
178
175
  }
179
- async function rememberIfRequested(options, project, projectSource, resolutionDetails) {
180
- if (options.remember) await options.context.stateStore.setRememberedProject({
181
- id: project.id,
182
- name: project.name,
183
- workspaceId: options.workspace.id
176
+ async function resolveBoundProjectTarget(options, projects, settings) {
177
+ if (options.explicitProject) return resolvedTarget(options.workspace, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
178
+ targetName: options.explicitProject,
179
+ targetNameSource: "explicit"
184
180
  });
181
+ if (settings.allowEnvProjectId && options.envProjectId) {
182
+ const project = projects.find((candidate) => candidate.id === options.envProjectId);
183
+ if (!project) throw projectNotFoundError(options.envProjectId, options.workspace);
184
+ return resolvedTarget(options.workspace, project, "env", {
185
+ targetName: options.envProjectId,
186
+ targetNameSource: "env"
187
+ });
188
+ }
189
+ const localPin = await readLocalResolutionPin(options.context.runtime.cwd);
190
+ if (localPin.kind === "invalid") throw localStateStaleError();
191
+ if (localPin.kind === "present") {
192
+ if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
193
+ const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
194
+ if (!project) throw localStateStaleError();
195
+ return resolvedTarget(options.workspace, project, "local-pin", {
196
+ targetName: project.name,
197
+ targetNameSource: "local-pin"
198
+ });
199
+ }
200
+ const platformMapping = await resolveDurablePlatformMapping();
201
+ if (platformMapping && platformMapping.workspace.id === options.workspace.id) return resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
202
+ targetName: platformMapping.name,
203
+ targetNameSource: "platform-mapping"
204
+ });
205
+ return null;
206
+ }
207
+ function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
185
208
  return {
186
- workspace: options.workspace,
209
+ workspace,
187
210
  project: toProjectSummary(project),
188
211
  resolution: {
189
212
  projectSource,
@@ -191,6 +214,11 @@ async function rememberIfRequested(options, project, projectSource, resolutionDe
191
214
  }
192
215
  };
193
216
  }
217
+ function buildProjectRecoveryCommands(commandName) {
218
+ const commands = ["prisma-cli project link <id-or-name>"];
219
+ if (commandName) commands.push(`prisma-cli ${commandName} --project <id-or-name>`);
220
+ return commands;
221
+ }
194
222
  function toProjectSummary(project) {
195
223
  return {
196
224
  id: project.id,
@@ -198,4 +226,4 @@ function toProjectSummary(project) {
198
226
  };
199
227
  }
200
228
  //#endregion
201
- export { inferTargetName, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
229
+ export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
@@ -1,6 +1,7 @@
1
1
  import { CliError, usageError } from "../../shell/errors.js";
2
- import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
2
+ import "../../shell/command-arguments.js";
3
3
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
4
+ import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
4
5
  //#region src/lib/project/setup.ts
5
6
  function isValidProjectSetupName(projectName) {
6
7
  return projectName.trim().length > 0;
@@ -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,21 +18,42 @@ 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) {
39
+ if (result.project === null) {
40
+ const lines = renderShow({
41
+ title: "This directory is not linked to a Prisma Project.",
42
+ descriptor,
43
+ fields: [{
44
+ key: "workspace",
45
+ value: result.workspace.name
46
+ }, {
47
+ key: "project",
48
+ value: "Not linked",
49
+ tone: "warning"
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
+ }
33
55
  return renderShow({
34
- title: "Showing the project Prisma resolves for this directory.",
56
+ title: "Showing this directory's Project binding.",
35
57
  descriptor,
36
58
  fields: [
37
59
  {
@@ -117,11 +139,9 @@ function formatProjectSource(source) {
117
139
  case "env": return "environment";
118
140
  case "local-pin": return "local pin";
119
141
  case "platform-mapping": return "platform mapping";
120
- case "remembered-local": return "remembered local context";
121
- case "package-name": return "package name";
122
- case "directory-name": return "directory name";
123
142
  case "created": return "created";
124
143
  case "prompt": return "prompt";
144
+ case "unbound": return "unbound";
125
145
  }
126
146
  }
127
147
  function formatGitConnectionDetail(status) {
@@ -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 };
@@ -95,7 +95,7 @@ const DESCRIPTORS = [
95
95
  "project",
96
96
  "show"
97
97
  ],
98
- description: "Show which project is active for this directory",
98
+ description: "Show this directory's Project binding",
99
99
  examples: ["prisma-cli project show", "prisma-cli project show --project proj_123 --json"]
100
100
  },
101
101
  {
@@ -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.40.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": {