@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.
- package/dist/controllers/app-env.js +6 -8
- package/dist/controllers/app.js +73 -45
- package/dist/controllers/project.js +62 -20
- package/dist/lib/project/resolution.js +124 -96
- package/dist/lib/project/setup.js +3 -5
- package/dist/presenters/project.js +35 -15
- package/dist/shell/command-arguments.js +6 -0
- package/dist/shell/command-meta.js +1 -1
- package/dist/shell/command-runner.js +4 -2
- package/dist/shell/errors.js +2 -0
- package/dist/shell/output.js +3 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
165
|
-
remember: true
|
|
163
|
+
commandName
|
|
166
164
|
})).project.id
|
|
167
165
|
};
|
|
168
166
|
}
|
package/dist/controllers/app.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
726
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
"",
|
|
1812
|
-
|
|
1813
|
-
"",
|
|
1814
|
-
|
|
1815
|
-
]
|
|
1816
|
-
|
|
1817
|
-
|
|
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}
|
|
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: [
|
|
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 {
|
|
6
|
-
import {
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
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
|
|
62
|
+
function localStateStaleError() {
|
|
103
63
|
return new CliError({
|
|
104
|
-
code: "
|
|
64
|
+
code: "LOCAL_STATE_STALE",
|
|
105
65
|
domain: "project",
|
|
106
|
-
summary: "
|
|
107
|
-
why:
|
|
108
|
-
fix:
|
|
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
|
|
71
|
+
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
|
|
111
72
|
});
|
|
112
73
|
}
|
|
113
|
-
function
|
|
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: "
|
|
87
|
+
code: "PROJECT_SETUP_REQUIRED",
|
|
116
88
|
domain: "project",
|
|
117
|
-
summary: "
|
|
118
|
-
why:
|
|
119
|
-
fix: "
|
|
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
|
|
161
|
-
|
|
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
|
|
180
|
-
if (options.
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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) {
|
|
@@ -95,7 +95,7 @@ const DESCRIPTORS = [
|
|
|
95
95
|
"project",
|
|
96
96
|
"show"
|
|
97
97
|
],
|
|
98
|
-
description: "Show
|
|
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;
|
package/dist/shell/errors.js
CHANGED
|
@@ -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
|
};
|
package/dist/shell/output.js
CHANGED
|
@@ -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) {
|