@prisma/cli 3.0.0-dev.40.1 → 3.0.0-dev.41.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,8 +11,8 @@ 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";
15
14
  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
16
  import { bindProjectToDirectory, formatCommandArgument, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
17
17
  import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
18
18
  import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
@@ -203,7 +203,7 @@ async function runAppDeploy(context, appName, options) {
203
203
  }
204
204
  async function runAppListDeploys(context, appName, projectRef) {
205
205
  ensurePreviewAppMode(context);
206
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
206
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app list-deploys" });
207
207
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
208
208
  if (!selectedApp) return {
209
209
  command: "app.list-deploys",
@@ -240,7 +240,7 @@ async function runAppListDeploys(context, appName, projectRef) {
240
240
  }
241
241
  async function runAppShow(context, appName, projectRef) {
242
242
  ensurePreviewAppMode(context);
243
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
243
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app show" });
244
244
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
245
245
  if (!selectedApp) return {
246
246
  command: "app.show",
@@ -316,7 +316,7 @@ async function runAppShowDeploy(context, deploymentId) {
316
316
  }
317
317
  async function runAppOpen(context, appName, projectRef) {
318
318
  ensurePreviewAppMode(context);
319
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
319
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
320
320
  const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
321
321
  if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
322
322
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
@@ -350,7 +350,7 @@ async function runAppOpen(context, appName, projectRef) {
350
350
  }
351
351
  async function runAppDomainAdd(context, hostname, options) {
352
352
  const normalizedHostname = normalizeDomainHostname(hostname);
353
- const target = await resolveAppDomainTarget(context, options);
353
+ const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
354
354
  const added = await target.provider.addDomain({
355
355
  appId: target.app.id,
356
356
  hostname: normalizedHostname
@@ -370,7 +370,7 @@ async function runAppDomainAdd(context, hostname, options) {
370
370
  }
371
371
  async function runAppDomainShow(context, hostname, options) {
372
372
  const normalizedHostname = normalizeDomainHostname(hostname);
373
- const target = await resolveAppDomainTarget(context, options);
373
+ const target = await resolveAppDomainTarget(context, options, `app domain show ${normalizedHostname}`);
374
374
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show");
375
375
  const detail = await target.provider.showDomain(domain.id).catch((error) => {
376
376
  throw domainCommandError("show", error, normalizedHostname);
@@ -387,7 +387,7 @@ async function runAppDomainShow(context, hostname, options) {
387
387
  }
388
388
  async function runAppDomainRemove(context, hostname, options) {
389
389
  const normalizedHostname = normalizeDomainHostname(hostname);
390
- const target = await resolveAppDomainTarget(context, options);
390
+ const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
391
391
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
392
392
  await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
393
393
  await target.provider.removeDomain(domain.id).catch((error) => {
@@ -406,7 +406,7 @@ async function runAppDomainRemove(context, hostname, options) {
406
406
  }
407
407
  async function runAppDomainRetry(context, hostname, options) {
408
408
  const normalizedHostname = normalizeDomainHostname(hostname);
409
- const target = await resolveAppDomainTarget(context, options);
409
+ const target = await resolveAppDomainTarget(context, options, `app domain retry ${normalizedHostname}`);
410
410
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry");
411
411
  const retried = await target.provider.retryDomain(domain.id).catch((error) => {
412
412
  throw domainCommandError("retry", error, normalizedHostname);
@@ -424,7 +424,7 @@ async function runAppDomainRetry(context, hostname, options) {
424
424
  async function runAppDomainWait(context, hostname, options) {
425
425
  const normalizedHostname = normalizeDomainHostname(hostname);
426
426
  const timeoutMs = parseDomainWaitTimeout(options?.timeout);
427
- const target = await resolveAppDomainTarget(context, options);
427
+ const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
428
428
  const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
429
429
  if (!context.flags.json && !context.flags.quiet) context.output.stderr.write([
430
430
  `app domain wait -> Waiting for ${normalizedHostname} to become active.`,
@@ -476,7 +476,7 @@ async function runAppDomainWait(context, hostname, options) {
476
476
  }
477
477
  async function runAppLogs(context, appName, deploymentId, projectRef) {
478
478
  ensurePreviewAppMode(context);
479
- const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef);
479
+ const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app logs" });
480
480
  const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
481
481
  if (!context.flags.json && !context.flags.quiet) {
482
482
  const lines = renderCommandHeader(context.ui, {
@@ -600,7 +600,7 @@ function writeLogRecord(context, record) {
600
600
  }
601
601
  async function runAppPromote(context, deploymentId, appName, projectRef) {
602
602
  ensurePreviewAppMode(context);
603
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
603
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
604
604
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
605
605
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
606
606
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -640,7 +640,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
640
640
  }
641
641
  async function runAppRollback(context, appName, deploymentId, projectRef) {
642
642
  ensurePreviewAppMode(context);
643
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
643
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
644
644
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
645
645
  const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
646
646
  throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
@@ -682,7 +682,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
682
682
  }
683
683
  async function runAppRemove(context, appName, projectRef) {
684
684
  ensurePreviewAppMode(context);
685
- const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
685
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
686
686
  const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
687
687
  await confirmAppRemoval(context, selectedApp);
688
688
  const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
@@ -703,7 +703,7 @@ async function runAppRemove(context, appName, projectRef) {
703
703
  nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
704
704
  };
705
705
  }
706
- async function resolveAppDomainTarget(context, options) {
706
+ async function resolveAppDomainTarget(context, options, commandName = "app domain") {
707
707
  ensurePreviewAppMode(context);
708
708
  const branch = resolveDomainBranch(options?.branchName);
709
709
  if (toBranchKind(branch.name) !== "production") throw new CliError({
@@ -717,13 +717,10 @@ async function resolveAppDomainTarget(context, options) {
717
717
  });
718
718
  const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
719
719
  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, {
720
+ const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
724
721
  branch,
725
- envProjectId,
726
- localPin
722
+ commandName,
723
+ envProjectId
727
724
  });
728
725
  const selectedApp = await resolveDomainAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
729
726
  explicitAppName: options?.appName,
@@ -1218,9 +1215,9 @@ async function listApps(context, provider, projectId, branchName) {
1218
1215
  domain: "project",
1219
1216
  summary: "Project not found",
1220
1217
  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.",
1218
+ fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect this directory's binding.",
1222
1219
  exitCode: 1,
1223
- nextSteps: ["prisma-cli project show", "prisma-cli app deploy --project <id-or-name>"]
1220
+ nextSteps: ["prisma-cli project show", "prisma-cli project link <id-or-name>"]
1224
1221
  });
1225
1222
  throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
1226
1223
  });
@@ -1280,8 +1277,9 @@ async function resolveProjectContext(context, client, explicitProject, options)
1280
1277
  context,
1281
1278
  workspace: authState.workspace,
1282
1279
  explicitProject,
1280
+ envProjectId: options?.envProjectId,
1283
1281
  listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
1284
- remember: true
1282
+ commandName: options?.commandName
1285
1283
  });
1286
1284
  const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
1287
1285
  return {
@@ -1862,10 +1860,14 @@ function localResolutionPinStaleError() {
1862
1860
  domain: "project",
1863
1861
  summary: "Local project binding is stale",
1864
1862
  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.`,
1863
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
1866
1864
  meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
1867
1865
  exitCode: 1,
1868
- nextSteps: ["prisma-cli app deploy"]
1866
+ nextSteps: [
1867
+ "prisma-cli project list",
1868
+ "prisma-cli project link <id-or-name>",
1869
+ "prisma-cli app deploy --project <id-or-name>"
1870
+ ]
1869
1871
  });
1870
1872
  }
1871
1873
  function readDeployEnvOverride(context, name) {
@@ -2,8 +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 { resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
6
- import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup } from "../lib/project/setup.js";
5
+ import { inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
6
+ import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
7
7
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
8
8
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
9
9
  import { requireAuthenticatedAuthState } from "./auth.js";
@@ -94,7 +94,7 @@ async function runGitConnect(context, gitUrl, options = {}) {
94
94
  if (isRealMode(context)) {
95
95
  const client = await requireComputeAuth(context.runtime.env);
96
96
  if (!client) throw authRequiredError();
97
- const target = await resolveProjectShowInRealMode(context, workspace, options.project);
97
+ const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
98
98
  const repository = await resolveRepositoryForConnect(context, gitUrl);
99
99
  const api = client;
100
100
  const existing = await readFirstSourceRepository(api, target.project.id);
@@ -129,7 +129,7 @@ async function runGitConnect(context, gitUrl, options = {}) {
129
129
  nextSteps: []
130
130
  };
131
131
  }
132
- const target = await resolveProjectShowInFixtureMode(context, workspace, options.project);
132
+ const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git connect");
133
133
  const repository = await resolveRepositoryForConnect(context, gitUrl);
134
134
  const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
135
135
  if (existingConnection) {
@@ -162,7 +162,7 @@ async function runGitDisconnect(context, options = {}) {
162
162
  if (isRealMode(context)) {
163
163
  const client = await requireComputeAuth(context.runtime.env);
164
164
  if (!client) throw authRequiredError();
165
- const target = await resolveProjectShowInRealMode(context, workspace, options.project);
165
+ const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
166
166
  const api = client;
167
167
  const existing = await readFirstSourceRepository(api, target.project.id);
168
168
  if (!existing) throw repoNotConnectedError();
@@ -178,7 +178,7 @@ async function runGitDisconnect(context, options = {}) {
178
178
  nextSteps: []
179
179
  };
180
180
  }
181
- const target = await resolveProjectShowInFixtureMode(context, workspace, options.project);
181
+ const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git disconnect");
182
182
  const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
183
183
  if (!existingConnection) throw repoNotConnectedError();
184
184
  await context.stateStore.clearRepositoryConnection(target.project.id);
@@ -193,6 +193,17 @@ async function runGitDisconnect(context, options = {}) {
193
193
  };
194
194
  }
195
195
  async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
196
+ const client = await requireComputeAuth(context.runtime.env);
197
+ if (!client) throw authRequiredError();
198
+ return inspectProjectBinding({
199
+ context,
200
+ workspace,
201
+ explicitProject,
202
+ listProjects: () => listRealWorkspaceProjects(client, workspace),
203
+ commandName: "project show"
204
+ });
205
+ }
206
+ async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
196
207
  const client = await requireComputeAuth(context.runtime.env);
197
208
  if (!client) throw authRequiredError();
198
209
  return resolveProjectTarget({
@@ -200,7 +211,7 @@ async function resolveProjectShowInRealMode(context, workspace, explicitProject)
200
211
  workspace,
201
212
  explicitProject,
202
213
  listProjects: () => listRealWorkspaceProjects(client, workspace),
203
- remember: false
214
+ commandName
204
215
  });
205
216
  }
206
217
  async function listRealProjectsForLink(context, workspace) {
@@ -209,12 +220,21 @@ async function listRealProjectsForLink(context, workspace) {
209
220
  return listRealWorkspaceProjects(client, workspace);
210
221
  }
211
222
  async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
223
+ return inspectProjectBinding({
224
+ context,
225
+ workspace,
226
+ explicitProject,
227
+ listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
228
+ commandName: "project show"
229
+ });
230
+ }
231
+ async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
212
232
  return resolveProjectTarget({
213
233
  context,
214
234
  workspace,
215
235
  explicitProject,
216
236
  listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
217
- remember: false
237
+ commandName
218
238
  });
219
239
  }
220
240
  async function listRealWorkspaceProjects(client, workspace) {
@@ -539,11 +559,5 @@ function repoConnectionFixForStatus(status) {
539
559
  if (status === 422) return "Make sure the GitHub App installation has access to this repository.";
540
560
  return "Re-run with --trace for the underlying API response details.";
541
561
  }
542
- function toProjectSummary(project) {
543
- return {
544
- id: project.id,
545
- name: project.name
546
- };
547
- }
548
562
  //#endregion
549
563
  export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
@@ -1,73 +1,31 @@
1
1
  import { CliError } from "../../shell/errors.js";
2
- import { canPrompt } from "../../shell/runtime.js";
2
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  //#region src/lib/project/resolution.ts
6
6
  async function resolveProjectTarget(options) {
7
7
  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"
8
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: true });
9
+ if (target) return target;
10
+ throw await projectSetupRequiredError({
11
+ cwd: options.context.runtime.cwd,
12
+ projects,
13
+ commandName: options.commandName
12
14
  });
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
- };
15
+ }
16
+ async function inspectProjectBinding(options) {
17
+ const projects = await options.listProjects();
18
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
19
+ if (target) return target;
65
20
  return {
66
- target: await rememberIfRequested(options, matched, "remembered-local", {
67
- targetName: remembered.name,
68
- targetNameSource: "remembered-local"
69
- }),
70
- stale: false
21
+ workspace: options.workspace,
22
+ project: null,
23
+ resolution: { projectSource: "unbound" },
24
+ ...await buildProjectSetupSuggestion({
25
+ cwd: options.context.runtime.cwd,
26
+ projects,
27
+ commandName: options.commandName ?? "project show"
28
+ })
71
29
  };
72
30
  }
73
31
  function projectNotFoundError(projectRef, workspace) {
@@ -99,26 +57,39 @@ function projectAmbiguousError(projectRef, matches) {
99
57
  nextSteps
100
58
  });
101
59
  }
102
- function projectUnresolvedError() {
60
+ function localStateStaleError() {
103
61
  return new CliError({
104
- code: "PROJECT_UNRESOLVED",
62
+ code: "LOCAL_STATE_STALE",
105
63
  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.",
64
+ summary: "Local project binding is stale",
65
+ why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
66
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
67
+ meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
109
68
  exitCode: 1,
110
- nextSteps: ["prisma-cli project list", "prisma-cli project show --project <id-or-name>"]
69
+ nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
111
70
  });
112
71
  }
113
- function localStateStaleError() {
72
+ async function buildProjectSetupSuggestion(options) {
73
+ const suggestedName = await inferTargetName(options.cwd);
74
+ const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
75
+ return {
76
+ suggestedProjectName: suggestedName.name,
77
+ suggestedProjectNameSource: suggestedName.source,
78
+ candidates,
79
+ recoveryCommands: buildProjectRecoveryCommands(options.commandName)
80
+ };
81
+ }
82
+ async function projectSetupRequiredError(options) {
83
+ const suggestion = await buildProjectSetupSuggestion(options);
114
84
  return new CliError({
115
- code: "LOCAL_STATE_STALE",
85
+ code: "PROJECT_SETUP_REQUIRED",
116
86
  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.",
87
+ summary: "Choose a Project before running this command",
88
+ 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.`,
89
+ fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
90
+ meta: { ...suggestion },
120
91
  exitCode: 1,
121
- nextSteps: ["prisma-cli project list"]
92
+ nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands]
122
93
  });
123
94
  }
124
95
  async function readPackageName(cwd) {
@@ -157,33 +128,46 @@ function resolveExplicitProject(projectRef, projects, workspace) {
157
128
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
158
129
  throw projectNotFoundError(projectRef, workspace);
159
130
  }
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;
131
+ function projectMatchesSuggestedName(project, suggestedName) {
132
+ return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
175
133
  }
176
134
  async function resolveDurablePlatformMapping() {
177
135
  return null;
178
136
  }
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
137
+ async function resolveBoundProjectTarget(options, projects, settings) {
138
+ if (options.explicitProject) return resolvedTarget(options.workspace, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
139
+ targetName: options.explicitProject,
140
+ targetNameSource: "explicit"
184
141
  });
142
+ if (settings.allowEnvProjectId && options.envProjectId) {
143
+ const project = projects.find((candidate) => candidate.id === options.envProjectId);
144
+ if (!project) throw projectNotFoundError(options.envProjectId, options.workspace);
145
+ return resolvedTarget(options.workspace, project, "env", {
146
+ targetName: options.envProjectId,
147
+ targetNameSource: "env"
148
+ });
149
+ }
150
+ const localPin = await readLocalResolutionPin(options.context.runtime.cwd);
151
+ if (localPin.kind === "invalid") throw localStateStaleError();
152
+ if (localPin.kind === "present") {
153
+ if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
154
+ const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
155
+ if (!project) throw localStateStaleError();
156
+ return resolvedTarget(options.workspace, project, "local-pin", {
157
+ targetName: project.name,
158
+ targetNameSource: "local-pin"
159
+ });
160
+ }
161
+ const platformMapping = await resolveDurablePlatformMapping();
162
+ if (platformMapping && platformMapping.workspace.id === options.workspace.id) return resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
163
+ targetName: platformMapping.name,
164
+ targetNameSource: "platform-mapping"
165
+ });
166
+ return null;
167
+ }
168
+ function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
185
169
  return {
186
- workspace: options.workspace,
170
+ workspace,
187
171
  project: toProjectSummary(project),
188
172
  resolution: {
189
173
  projectSource,
@@ -191,6 +175,11 @@ async function rememberIfRequested(options, project, projectSource, resolutionDe
191
175
  }
192
176
  };
193
177
  }
178
+ function buildProjectRecoveryCommands(commandName) {
179
+ const commands = ["prisma-cli project link <id-or-name>"];
180
+ if (commandName) commands.push(`prisma-cli ${commandName} --project <id-or-name>`);
181
+ return commands;
182
+ }
194
183
  function toProjectSummary(project) {
195
184
  return {
196
185
  id: project.id,
@@ -198,4 +187,4 @@ function toProjectSummary(project) {
198
187
  };
199
188
  }
200
189
  //#endregion
201
- export { inferTargetName, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
190
+ export { inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
@@ -1,6 +1,6 @@
1
1
  import { CliError, usageError } from "../../shell/errors.js";
2
- import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
3
2
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
3
+ import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
4
4
  //#region src/lib/project/setup.ts
5
5
  function isValidProjectSetupName(projectName) {
6
6
  return projectName.trim().length > 0;
@@ -30,8 +30,35 @@ function serializeProjectList(result) {
30
30
  });
31
31
  }
32
32
  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
+ {
38
+ key: "workspace",
39
+ value: result.workspace.name
40
+ },
41
+ {
42
+ key: "project",
43
+ value: "unbound",
44
+ 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);
33
60
  return renderShow({
34
- title: "Showing the project Prisma resolves for this directory.",
61
+ title: "Showing this directory's Project binding.",
35
62
  descriptor,
36
63
  fields: [
37
64
  {
@@ -117,13 +144,21 @@ function formatProjectSource(source) {
117
144
  case "env": return "environment";
118
145
  case "local-pin": return "local pin";
119
146
  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
147
  case "created": return "created";
124
148
  case "prompt": return "prompt";
149
+ case "unbound": return "unbound";
125
150
  }
126
151
  }
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
+ }
127
162
  function formatGitConnectionDetail(status) {
128
163
  switch (status) {
129
164
  case "active": return "GitHub branch automation is active for this project.";
@@ -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
  {
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.41.1",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {