@prisma/cli 3.0.0-alpha.6 → 3.0.0-alpha.8
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/adapters/local-state.js +1 -1
- package/dist/commands/app/index.js +9 -1
- package/dist/controllers/app.js +690 -57
- package/dist/lib/app/bun-project.js +1 -1
- package/dist/lib/app/deploy-output.js +15 -0
- package/dist/lib/app/local-dev.js +1 -1
- package/dist/lib/app/preview-build.js +1 -1
- package/dist/lib/app/preview-interaction.js +2 -35
- package/dist/lib/app/preview-progress.js +43 -58
- package/dist/lib/app/preview-provider.js +110 -22
- package/dist/lib/auth/client.js +1 -1
- package/dist/lib/project/local-pin.js +51 -0
- package/dist/lib/project/resolution.js +71 -21
- package/dist/presenters/app.js +16 -37
- package/dist/presenters/project.js +3 -0
- package/dist/shell/command-meta.js +4 -4
- package/dist/shell/errors.js +2 -0
- package/dist/shell/output.js +11 -0
- package/dist/shell/prompt.js +12 -2
- package/package.json +1 -1
package/dist/controllers/app.js
CHANGED
|
@@ -4,21 +4,34 @@ import { CliError, authRequiredError, featureUnavailableError, usageError, works
|
|
|
4
4
|
import { renderCommandHeader } from "../shell/ui.js";
|
|
5
5
|
import { writeJsonEvent } from "../shell/output.js";
|
|
6
6
|
import { canPrompt } from "../shell/runtime.js";
|
|
7
|
-
import { textPrompt } from "../shell/prompt.js";
|
|
7
|
+
import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
|
|
8
8
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
9
9
|
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
10
10
|
import { parseEnvAssignments } from "../lib/app/env-vars.js";
|
|
11
|
+
import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
|
|
12
|
+
import { readBunPackageJson } from "../lib/app/bun-project.js";
|
|
11
13
|
import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
|
|
12
|
-
import { resolveProjectTarget } from "../lib/project/resolution.js";
|
|
14
|
+
import { inferTargetName, projectNotFoundError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
15
|
+
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
13
16
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
14
|
-
import { PREVIEW_DEFAULT_REGION
|
|
15
|
-
import { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
|
|
17
|
+
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
18
|
+
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
|
|
16
19
|
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
17
20
|
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
18
21
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
19
22
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
23
|
+
import { access, readFile } from "node:fs/promises";
|
|
24
|
+
import path from "node:path";
|
|
20
25
|
import open from "open";
|
|
21
26
|
//#region src/controllers/app.ts
|
|
27
|
+
const DEPLOY_FRAMEWORKS = [
|
|
28
|
+
"nextjs",
|
|
29
|
+
"hono",
|
|
30
|
+
"tanstack-start"
|
|
31
|
+
];
|
|
32
|
+
const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
|
|
33
|
+
const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
|
|
34
|
+
const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
|
|
22
35
|
function isRealMode(context) {
|
|
23
36
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
24
37
|
}
|
|
@@ -80,15 +93,75 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
|
|
|
80
93
|
}
|
|
81
94
|
async function runAppDeploy(context, appName, options) {
|
|
82
95
|
ensurePreviewAppMode(context);
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const
|
|
96
|
+
const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
|
|
97
|
+
const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
|
|
98
|
+
const skipLocalPin = Boolean(envProjectId || envAppId);
|
|
99
|
+
const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
|
|
100
|
+
if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
|
|
101
|
+
const explicitBuildType = Boolean(options?.buildType && options.buildType !== "auto");
|
|
102
|
+
const branch = await resolveDeployBranch(context, options?.branchName);
|
|
103
|
+
if (options?.httpPort) parseDeployHttpPort(options.httpPort);
|
|
104
|
+
let framework = await resolveDeployFramework(context, {
|
|
105
|
+
requestedFramework: options?.framework,
|
|
106
|
+
requestedBuildType: options?.buildType,
|
|
107
|
+
explicitBuildType
|
|
108
|
+
});
|
|
109
|
+
let runtime = resolveDeployRuntime(options?.httpPort, framework);
|
|
110
|
+
assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
|
|
86
111
|
const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
|
|
87
|
-
const
|
|
88
|
-
const
|
|
112
|
+
const firstDeploy = !skipLocalPin && localPin.kind === "missing";
|
|
113
|
+
const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
|
|
114
|
+
allowCreate: true,
|
|
115
|
+
branch,
|
|
116
|
+
envProjectId,
|
|
117
|
+
localPin
|
|
118
|
+
});
|
|
119
|
+
const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
|
|
120
|
+
explicitAppName: appName,
|
|
121
|
+
explicitAppId: envAppId,
|
|
122
|
+
firstDeploy,
|
|
123
|
+
inferName: () => inferTargetName(context.runtime.cwd)
|
|
124
|
+
});
|
|
125
|
+
await maybeRenderDeploySetupBlock(context, {
|
|
126
|
+
firstDeploy: selectedApp.firstDeploy,
|
|
127
|
+
workspaceName: target.workspace.name,
|
|
128
|
+
projectName: target.project.name,
|
|
129
|
+
projectAnnotation: annotationForProjectResolution(target.resolution),
|
|
130
|
+
branchName: target.branch.name,
|
|
131
|
+
branchAnnotation: branch.annotation,
|
|
132
|
+
appName: selectedApp.displayName,
|
|
133
|
+
appAnnotation: selectedApp.annotation,
|
|
134
|
+
framework,
|
|
135
|
+
runtime
|
|
136
|
+
});
|
|
137
|
+
const customized = await maybeCustomizeDeploySettings(context, {
|
|
138
|
+
framework,
|
|
139
|
+
runtime,
|
|
140
|
+
firstDeploy: selectedApp.firstDeploy,
|
|
141
|
+
explicitFramework: Boolean(options?.framework),
|
|
142
|
+
explicitBuildType,
|
|
143
|
+
explicitHttpPort: Boolean(options?.httpPort)
|
|
144
|
+
});
|
|
145
|
+
framework = customized.framework;
|
|
146
|
+
runtime = customized.runtime;
|
|
147
|
+
const buildType = framework.buildType;
|
|
148
|
+
assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
|
|
149
|
+
const portMapping = parseDeployPortMapping(String(runtime.port));
|
|
150
|
+
const shouldWriteLocalPin = firstDeploy && !skipLocalPin;
|
|
151
|
+
if (shouldWriteLocalPin) {
|
|
152
|
+
await writeLocalResolutionPin(context.runtime.cwd, {
|
|
153
|
+
workspaceId: target.workspace.id,
|
|
154
|
+
projectId: target.project.id
|
|
155
|
+
});
|
|
156
|
+
await ensureLocalResolutionPinGitignore(context.runtime.cwd);
|
|
157
|
+
maybeRenderLocalPinBound(context, target.project.name);
|
|
158
|
+
}
|
|
159
|
+
const progressState = createPreviewDeployProgressState();
|
|
160
|
+
const deployStartedAt = Date.now();
|
|
89
161
|
const deployResult = await provider.deployApp({
|
|
90
162
|
cwd: context.runtime.cwd,
|
|
91
163
|
projectId,
|
|
164
|
+
branchName: target.branch.name,
|
|
92
165
|
appId: selectedApp.appId,
|
|
93
166
|
appName: selectedApp.appName,
|
|
94
167
|
region: selectedApp.region,
|
|
@@ -96,11 +169,12 @@ async function runAppDeploy(context, appName, options) {
|
|
|
96
169
|
buildType,
|
|
97
170
|
portMapping,
|
|
98
171
|
envVars,
|
|
99
|
-
interaction:
|
|
100
|
-
progress: createPreviewDeployProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
|
|
172
|
+
interaction: void 0,
|
|
173
|
+
progress: createPreviewDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
|
|
101
174
|
}).catch((error) => {
|
|
102
|
-
throw
|
|
175
|
+
throw appDeployFailedError(error, progressState);
|
|
103
176
|
});
|
|
177
|
+
const deployDurationMs = Date.now() - deployStartedAt;
|
|
104
178
|
await context.stateStore.setSelectedApp(projectId, {
|
|
105
179
|
id: deployResult.app.id,
|
|
106
180
|
name: deployResult.app.name
|
|
@@ -117,7 +191,12 @@ async function runAppDeploy(context, appName, options) {
|
|
|
117
191
|
id: deployResult.app.id,
|
|
118
192
|
name: deployResult.app.name
|
|
119
193
|
},
|
|
120
|
-
deployment: deployResult.deployment
|
|
194
|
+
deployment: deployResult.deployment,
|
|
195
|
+
durationMs: deployDurationMs,
|
|
196
|
+
localPin: shouldWriteLocalPin ? {
|
|
197
|
+
path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
198
|
+
written: true
|
|
199
|
+
} : void 0
|
|
121
200
|
},
|
|
122
201
|
warnings: [],
|
|
123
202
|
nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
|
|
@@ -130,8 +209,8 @@ async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
|
|
|
130
209
|
commandName: "update-env",
|
|
131
210
|
requireAtLeastOne: true
|
|
132
211
|
});
|
|
133
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
134
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
212
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
213
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
135
214
|
if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The resolved project does not have any deployed app yet.");
|
|
136
215
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
137
216
|
throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
|
|
@@ -168,8 +247,8 @@ async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
|
|
|
168
247
|
async function runAppListEnv(context, appName, projectRef) {
|
|
169
248
|
ensurePreviewAppMode(context);
|
|
170
249
|
emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
|
|
171
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
172
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
250
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
251
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
173
252
|
if (!selectedApp) return {
|
|
174
253
|
command: "app.list-env",
|
|
175
254
|
result: {
|
|
@@ -255,8 +334,8 @@ async function runAppListEnv(context, appName, projectRef) {
|
|
|
255
334
|
}
|
|
256
335
|
async function runAppListDeploys(context, appName, projectRef) {
|
|
257
336
|
ensurePreviewAppMode(context);
|
|
258
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
259
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
337
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
338
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
260
339
|
if (!selectedApp) return {
|
|
261
340
|
command: "app.list-deploys",
|
|
262
341
|
result: {
|
|
@@ -292,8 +371,8 @@ async function runAppListDeploys(context, appName, projectRef) {
|
|
|
292
371
|
}
|
|
293
372
|
async function runAppShow(context, appName, projectRef) {
|
|
294
373
|
ensurePreviewAppMode(context);
|
|
295
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
296
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
374
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
375
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
297
376
|
if (!selectedApp) return {
|
|
298
377
|
command: "app.show",
|
|
299
378
|
result: {
|
|
@@ -368,8 +447,8 @@ async function runAppShowDeploy(context, deploymentId) {
|
|
|
368
447
|
}
|
|
369
448
|
async function runAppOpen(context, appName, projectRef) {
|
|
370
449
|
ensurePreviewAppMode(context);
|
|
371
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
372
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
450
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
451
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
373
452
|
if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
|
|
374
453
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
375
454
|
throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
|
|
@@ -402,8 +481,8 @@ async function runAppOpen(context, appName, projectRef) {
|
|
|
402
481
|
}
|
|
403
482
|
async function runAppLogs(context, appName, deploymentId, projectRef) {
|
|
404
483
|
ensurePreviewAppMode(context);
|
|
405
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
406
|
-
const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, appName);
|
|
484
|
+
const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
485
|
+
const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
|
|
407
486
|
if (!context.flags.json && !context.flags.quiet) {
|
|
408
487
|
const lines = renderCommandHeader(context.ui, {
|
|
409
488
|
commandLabel: "app logs",
|
|
@@ -433,9 +512,9 @@ async function runAppLogs(context, appName, deploymentId, projectRef) {
|
|
|
433
512
|
throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
|
|
434
513
|
});
|
|
435
514
|
}
|
|
436
|
-
async function resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) {
|
|
515
|
+
async function resolveExplicitLogDeployment(context, provider, projectId, branchName, appName, deploymentId) {
|
|
437
516
|
if (appName) {
|
|
438
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
517
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
|
|
439
518
|
if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
|
|
440
519
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
441
520
|
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
@@ -471,7 +550,7 @@ async function resolveExplicitLogDeployment(context, provider, projectId, appNam
|
|
|
471
550
|
exitCode: 1,
|
|
472
551
|
nextSteps: ["prisma-cli app list-deploys"]
|
|
473
552
|
});
|
|
474
|
-
const resolvedProjectApp = (await listApps(context, provider, projectId)).find((app) => app.id === shown.app?.id);
|
|
553
|
+
const resolvedProjectApp = (await listApps(context, provider, projectId, branchName)).find((app) => app.id === shown.app?.id);
|
|
475
554
|
if (!resolvedProjectApp) throw new CliError({
|
|
476
555
|
code: "DEPLOYMENT_NOT_FOUND",
|
|
477
556
|
domain: "app",
|
|
@@ -490,8 +569,8 @@ async function resolveExplicitLogDeployment(context, provider, projectId, appNam
|
|
|
490
569
|
deployment: shown.deployment
|
|
491
570
|
};
|
|
492
571
|
}
|
|
493
|
-
async function resolveLiveLogDeployment(context, provider, projectId, appName) {
|
|
494
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
572
|
+
async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
|
|
573
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
|
|
495
574
|
if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
|
|
496
575
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
497
576
|
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
@@ -526,8 +605,8 @@ function writeLogRecord(context, record) {
|
|
|
526
605
|
}
|
|
527
606
|
async function runAppPromote(context, deploymentId, appName, projectRef) {
|
|
528
607
|
ensurePreviewAppMode(context);
|
|
529
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
530
|
-
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "promote");
|
|
608
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
609
|
+
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
|
|
531
610
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
532
611
|
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
533
612
|
});
|
|
@@ -566,8 +645,8 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
|
|
|
566
645
|
}
|
|
567
646
|
async function runAppRollback(context, appName, deploymentId, projectRef) {
|
|
568
647
|
ensurePreviewAppMode(context);
|
|
569
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
570
|
-
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "rollback");
|
|
648
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
649
|
+
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
|
|
571
650
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
572
651
|
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
573
652
|
});
|
|
@@ -608,8 +687,8 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
|
|
|
608
687
|
}
|
|
609
688
|
async function runAppRemove(context, appName, projectRef) {
|
|
610
689
|
ensurePreviewAppMode(context);
|
|
611
|
-
const { provider, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
612
|
-
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId), appName, "remove");
|
|
690
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
691
|
+
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
|
|
613
692
|
await confirmAppRemoval(context, selectedApp);
|
|
614
693
|
const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
|
|
615
694
|
throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
|
|
@@ -629,30 +708,104 @@ async function runAppRemove(context, appName, projectRef) {
|
|
|
629
708
|
nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
|
|
630
709
|
};
|
|
631
710
|
}
|
|
632
|
-
async function
|
|
633
|
-
if (explicitAppName) {
|
|
634
|
-
const
|
|
711
|
+
async function resolveDeployAppSelection(context, projectId, apps, options) {
|
|
712
|
+
if (options.explicitAppName) {
|
|
713
|
+
const matches = findAppsByName(apps, options.explicitAppName);
|
|
714
|
+
if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.explicitAppName, options.firstDeploy);
|
|
715
|
+
const matched = matches[0];
|
|
635
716
|
if (matched) return {
|
|
636
717
|
appId: matched.id,
|
|
637
|
-
|
|
718
|
+
displayName: matched.name,
|
|
719
|
+
annotation: "set by --app",
|
|
720
|
+
firstDeploy: options.firstDeploy
|
|
638
721
|
};
|
|
639
722
|
return {
|
|
640
|
-
appName: explicitAppName,
|
|
723
|
+
appName: options.explicitAppName,
|
|
641
724
|
region: PREVIEW_DEFAULT_REGION,
|
|
642
|
-
|
|
725
|
+
displayName: options.explicitAppName,
|
|
726
|
+
annotation: "set by --app",
|
|
727
|
+
firstDeploy: options.firstDeploy
|
|
643
728
|
};
|
|
644
729
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
730
|
+
if (options.explicitAppId) {
|
|
731
|
+
const matched = apps.find((app) => app.id === options.explicitAppId);
|
|
732
|
+
if (!matched) throw usageError("Selected app does not exist in the resolved project", `The app "${options.explicitAppId}" from ${PRISMA_APP_ID_ENV_VAR} could not be found in resolved project "${projectId}".`, `Unset ${PRISMA_APP_ID_ENV_VAR}, pass --app <name>, or choose an app from prisma-cli app list-deploys.`, ["prisma-cli app list-deploys"], "app");
|
|
733
|
+
return {
|
|
649
734
|
appId: matched.id,
|
|
650
|
-
|
|
735
|
+
displayName: matched.name,
|
|
736
|
+
annotation: `from ${PRISMA_APP_ID_ENV_VAR}`,
|
|
737
|
+
firstDeploy: options.firstDeploy
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const inferredName = await options.inferName();
|
|
741
|
+
const matches = findAppsByName(apps, inferredName.name);
|
|
742
|
+
if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
|
|
743
|
+
const matched = matches[0];
|
|
744
|
+
if (matched) return {
|
|
745
|
+
appId: matched.id,
|
|
746
|
+
displayName: matched.name,
|
|
747
|
+
annotation: "existing app on this branch",
|
|
748
|
+
firstDeploy: options.firstDeploy
|
|
749
|
+
};
|
|
750
|
+
return {
|
|
751
|
+
appName: inferredName.name,
|
|
752
|
+
region: PREVIEW_DEFAULT_REGION,
|
|
753
|
+
displayName: inferredName.name,
|
|
754
|
+
annotation: inferredName.source === "package-name" ? "created from package.json" : "created from directory name",
|
|
755
|
+
firstDeploy: options.firstDeploy
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
async function resolveAmbiguousDeployApp(context, matches, targetName, firstDeploy) {
|
|
759
|
+
if (canPrompt(context)) {
|
|
760
|
+
const createNew = "__create_new_app__";
|
|
761
|
+
const cancel = "__cancel__";
|
|
762
|
+
const selected = await selectPrompt({
|
|
763
|
+
input: context.runtime.stdin,
|
|
764
|
+
output: context.runtime.stderr,
|
|
765
|
+
message: `Multiple apps are named "${targetName}"`,
|
|
766
|
+
choices: [
|
|
767
|
+
...sortApps(matches).map((app) => ({
|
|
768
|
+
label: `${app.name} (${app.id})`,
|
|
769
|
+
value: app
|
|
770
|
+
})),
|
|
771
|
+
{
|
|
772
|
+
label: `Create a new app named "${targetName}"`,
|
|
773
|
+
value: createNew
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
label: "Cancel",
|
|
777
|
+
value: cancel
|
|
778
|
+
}
|
|
779
|
+
]
|
|
780
|
+
});
|
|
781
|
+
if (selected === cancel) throw usageError("App selection canceled", "The command was canceled before an app was selected.", "Re-run the command and choose an app, or pass --app <name>.", ["prisma-cli app deploy --app <name>"], "app");
|
|
782
|
+
if (selected === createNew) return {
|
|
783
|
+
appName: targetName,
|
|
784
|
+
region: PREVIEW_DEFAULT_REGION,
|
|
785
|
+
displayName: targetName,
|
|
786
|
+
annotation: "created from package.json",
|
|
787
|
+
firstDeploy
|
|
788
|
+
};
|
|
789
|
+
return {
|
|
790
|
+
appId: selected.id,
|
|
791
|
+
displayName: selected.name,
|
|
792
|
+
annotation: "selected by you",
|
|
793
|
+
firstDeploy
|
|
651
794
|
};
|
|
652
|
-
if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the resolved project.", "Pass --app <name>, or rerun prisma-cli app deploy in a TTY to choose or create an app again.", ["prisma-cli app deploy"], "app");
|
|
653
795
|
}
|
|
654
|
-
|
|
655
|
-
|
|
796
|
+
throw new CliError({
|
|
797
|
+
code: "APP_AMBIGUOUS",
|
|
798
|
+
domain: "app",
|
|
799
|
+
summary: "App resolution is ambiguous",
|
|
800
|
+
why: `Multiple apps matched "${targetName}".`,
|
|
801
|
+
fix: "Pass --app <name> to choose the app explicitly.",
|
|
802
|
+
meta: { candidates: matches.map((app) => ({
|
|
803
|
+
id: app.id,
|
|
804
|
+
name: app.name
|
|
805
|
+
})) },
|
|
806
|
+
exitCode: 2,
|
|
807
|
+
nextSteps: ["prisma-cli app deploy --app <name>"]
|
|
808
|
+
});
|
|
656
809
|
}
|
|
657
810
|
async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
|
|
658
811
|
if (explicitAppName) {
|
|
@@ -767,8 +920,8 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
|
|
|
767
920
|
nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
|
|
768
921
|
});
|
|
769
922
|
}
|
|
770
|
-
async function listApps(context, provider, projectId) {
|
|
771
|
-
return provider.listApps(projectId).then(sortApps).catch((error) => {
|
|
923
|
+
async function listApps(context, provider, projectId, branchName) {
|
|
924
|
+
return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
|
|
772
925
|
if (isMissingProjectError(error)) throw new CliError({
|
|
773
926
|
code: "PROJECT_NOT_FOUND",
|
|
774
927
|
domain: "project",
|
|
@@ -819,6 +972,16 @@ async function requireProviderAndProjectContext(context, explicitProject, option
|
|
|
819
972
|
projectId: target.project.id
|
|
820
973
|
};
|
|
821
974
|
}
|
|
975
|
+
async function requireProviderAndDeployProjectContext(context, explicitProject, options) {
|
|
976
|
+
const { client, provider } = await requirePreviewAppProviderWithClient(context);
|
|
977
|
+
const target = await resolveDeployProjectContext(context, client, provider, explicitProject, options);
|
|
978
|
+
return {
|
|
979
|
+
client,
|
|
980
|
+
provider,
|
|
981
|
+
target,
|
|
982
|
+
projectId: target.project.id
|
|
983
|
+
};
|
|
984
|
+
}
|
|
822
985
|
async function resolveProjectContext(context, client, provider, explicitProject, options) {
|
|
823
986
|
const authState = await requireAuthenticatedAuthState(context);
|
|
824
987
|
if (!authState.workspace) throw workspaceRequiredError();
|
|
@@ -845,17 +1008,404 @@ async function resolveProjectContext(context, client, provider, explicitProject,
|
|
|
845
1008
|
prompt: createSelectPromptPort(context),
|
|
846
1009
|
remember: true
|
|
847
1010
|
});
|
|
848
|
-
const
|
|
1011
|
+
const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
|
|
849
1012
|
return {
|
|
850
1013
|
...resolved,
|
|
851
1014
|
branch: {
|
|
852
|
-
name:
|
|
853
|
-
kind: toBranchKind(
|
|
1015
|
+
name: branch.name,
|
|
1016
|
+
kind: toBranchKind(branch.name)
|
|
854
1017
|
}
|
|
855
1018
|
};
|
|
856
1019
|
}
|
|
1020
|
+
async function resolveDeployProjectContext(context, client, provider, explicitProject, options) {
|
|
1021
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
1022
|
+
if (!workspace) throw workspaceRequiredError();
|
|
1023
|
+
const branch = options.branch ?? await resolveDeployBranch(context, void 0);
|
|
1024
|
+
const projects = await listRealWorkspaceProjects(client, workspace);
|
|
1025
|
+
const createProject = options.allowCreate ? async (name) => {
|
|
1026
|
+
const project = await provider.createProject({ name }).catch((error) => {
|
|
1027
|
+
throw createProjectOnFirstDeployError({
|
|
1028
|
+
error,
|
|
1029
|
+
inferredName: name,
|
|
1030
|
+
workspaceName: workspace.name
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
return {
|
|
1034
|
+
id: project.id,
|
|
1035
|
+
name: project.name,
|
|
1036
|
+
workspace
|
|
1037
|
+
};
|
|
1038
|
+
} : void 0;
|
|
1039
|
+
if (explicitProject) return withDeployBranch(await resolveProjectTarget({
|
|
1040
|
+
context,
|
|
1041
|
+
workspace,
|
|
1042
|
+
explicitProject,
|
|
1043
|
+
listProjects: async () => projects,
|
|
1044
|
+
createProject,
|
|
1045
|
+
allowCreate: options.allowCreate,
|
|
1046
|
+
prompt: createSelectPromptPort(context),
|
|
1047
|
+
remember: true
|
|
1048
|
+
}), branch);
|
|
1049
|
+
if (options.envProjectId) {
|
|
1050
|
+
const project = projects.find((candidate) => candidate.id === options.envProjectId);
|
|
1051
|
+
if (!project) throw projectNotFoundError(options.envProjectId, workspace);
|
|
1052
|
+
return withDeployBranch({
|
|
1053
|
+
workspace,
|
|
1054
|
+
project: toProjectSummary(project),
|
|
1055
|
+
resolution: {
|
|
1056
|
+
projectSource: "env",
|
|
1057
|
+
targetName: options.envProjectId,
|
|
1058
|
+
targetNameSource: "env"
|
|
1059
|
+
}
|
|
1060
|
+
}, branch);
|
|
1061
|
+
}
|
|
1062
|
+
const localPin = options.localPin;
|
|
1063
|
+
if (localPin.kind === "present") {
|
|
1064
|
+
if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
|
|
1065
|
+
const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
|
|
1066
|
+
if (!project) throw localResolutionPinStaleError();
|
|
1067
|
+
return withDeployBranch({
|
|
1068
|
+
workspace,
|
|
1069
|
+
project: toProjectSummary(project),
|
|
1070
|
+
resolution: {
|
|
1071
|
+
projectSource: "local-pin",
|
|
1072
|
+
targetName: project.name,
|
|
1073
|
+
targetNameSource: "local-pin"
|
|
1074
|
+
}
|
|
1075
|
+
}, branch);
|
|
1076
|
+
}
|
|
1077
|
+
return withDeployBranch(await resolveProjectTarget({
|
|
1078
|
+
context,
|
|
1079
|
+
workspace,
|
|
1080
|
+
listProjects: async () => projects,
|
|
1081
|
+
createProject,
|
|
1082
|
+
allowCreate: options.allowCreate,
|
|
1083
|
+
prompt: createSelectPromptPort(context),
|
|
1084
|
+
remember: true
|
|
1085
|
+
}), branch);
|
|
1086
|
+
}
|
|
1087
|
+
function withDeployBranch(target, branch) {
|
|
1088
|
+
return {
|
|
1089
|
+
...target,
|
|
1090
|
+
branch: {
|
|
1091
|
+
name: branch.name,
|
|
1092
|
+
kind: toBranchKind(branch.name)
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function toProjectSummary(project) {
|
|
1097
|
+
return {
|
|
1098
|
+
id: project.id,
|
|
1099
|
+
name: project.name
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
857
1102
|
function toBranchKind(name) {
|
|
858
|
-
return name === "production" ? "production" : "preview";
|
|
1103
|
+
return name === "production" || name === "main" ? "production" : "preview";
|
|
1104
|
+
}
|
|
1105
|
+
async function resolveDeployBranch(context, explicitBranchName) {
|
|
1106
|
+
if (explicitBranchName) return {
|
|
1107
|
+
name: explicitBranchName,
|
|
1108
|
+
annotation: "set by --branch"
|
|
1109
|
+
};
|
|
1110
|
+
const gitBranch = await readLocalGitBranch(context.runtime.cwd);
|
|
1111
|
+
if (gitBranch) return {
|
|
1112
|
+
name: gitBranch,
|
|
1113
|
+
annotation: "from local Git branch"
|
|
1114
|
+
};
|
|
1115
|
+
return {
|
|
1116
|
+
name: "main",
|
|
1117
|
+
annotation: "default"
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
async function readLocalGitBranch(cwd) {
|
|
1121
|
+
const headPath = await resolveGitHeadPath(path.join(cwd, ".git"));
|
|
1122
|
+
if (!headPath) return null;
|
|
1123
|
+
try {
|
|
1124
|
+
const head = (await readFile(headPath, "utf8")).trim();
|
|
1125
|
+
if (head.startsWith("ref: refs/heads/")) return head.slice(16);
|
|
1126
|
+
} catch {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
async function resolveGitHeadPath(gitPath) {
|
|
1132
|
+
try {
|
|
1133
|
+
const raw = await readFile(gitPath, "utf8");
|
|
1134
|
+
if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
|
|
1135
|
+
} catch {}
|
|
1136
|
+
try {
|
|
1137
|
+
await access(path.join(gitPath, "HEAD"));
|
|
1138
|
+
return path.join(gitPath, "HEAD");
|
|
1139
|
+
} catch {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async function resolveDeployFramework(context, options) {
|
|
1144
|
+
if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
|
|
1145
|
+
if (options.explicitBuildType) {
|
|
1146
|
+
const buildType = normalizeBuildType(options.requestedBuildType);
|
|
1147
|
+
if (buildType !== "auto") return {
|
|
1148
|
+
key: buildType,
|
|
1149
|
+
buildType,
|
|
1150
|
+
displayName: formatBuildTypeName(buildType),
|
|
1151
|
+
annotation: "set by --build-type"
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const detected = await detectDeployFramework(context.runtime.cwd);
|
|
1155
|
+
if (detected) return detected;
|
|
1156
|
+
throw frameworkNotDetectedError(context.runtime.cwd);
|
|
1157
|
+
}
|
|
1158
|
+
function resolveDeployRuntime(requestedHttpPort, framework) {
|
|
1159
|
+
if (requestedHttpPort) return {
|
|
1160
|
+
port: parseDeployHttpPort(requestedHttpPort),
|
|
1161
|
+
annotation: "set by --http-port"
|
|
1162
|
+
};
|
|
1163
|
+
return {
|
|
1164
|
+
port: FRAMEWORK_DEFAULT_HTTP_PORT,
|
|
1165
|
+
annotation: `${framework.displayName} default`
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
async function detectDeployFramework(cwd) {
|
|
1169
|
+
const packageJson = await readBunPackageJson(cwd);
|
|
1170
|
+
const nextConfig = await detectNextConfig(cwd);
|
|
1171
|
+
if (nextConfig.exists || hasPackageDependency(packageJson, "next")) return {
|
|
1172
|
+
key: "nextjs",
|
|
1173
|
+
buildType: "nextjs",
|
|
1174
|
+
displayName: "Next.js",
|
|
1175
|
+
annotation: nextConfig.standalone ? "standalone output detected" : nextConfig.exists ? "detected from next.config" : "detected from package.json"
|
|
1176
|
+
};
|
|
1177
|
+
if (hasPackageDependency(packageJson, "hono")) return {
|
|
1178
|
+
key: "hono",
|
|
1179
|
+
buildType: "bun",
|
|
1180
|
+
displayName: "Hono",
|
|
1181
|
+
annotation: "detected from package.json"
|
|
1182
|
+
};
|
|
1183
|
+
if (hasPackageDependency(packageJson, "@tanstack/start")) return {
|
|
1184
|
+
key: "tanstack-start",
|
|
1185
|
+
buildType: "tanstack-start",
|
|
1186
|
+
displayName: "TanStack Start",
|
|
1187
|
+
annotation: "detected from package.json"
|
|
1188
|
+
};
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
async function detectNextConfig(cwd) {
|
|
1192
|
+
for (const candidate of [
|
|
1193
|
+
"next.config.js",
|
|
1194
|
+
"next.config.mjs",
|
|
1195
|
+
"next.config.cjs",
|
|
1196
|
+
"next.config.ts"
|
|
1197
|
+
]) {
|
|
1198
|
+
const filePath = path.join(cwd, candidate);
|
|
1199
|
+
try {
|
|
1200
|
+
const content = await readFile(filePath, "utf8");
|
|
1201
|
+
return {
|
|
1202
|
+
exists: true,
|
|
1203
|
+
standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content)
|
|
1204
|
+
};
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
if (error.code !== "ENOENT") throw error;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return {
|
|
1210
|
+
exists: false,
|
|
1211
|
+
standalone: false
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function hasPackageDependency(packageJson, dependencyName) {
|
|
1215
|
+
return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
|
|
1216
|
+
}
|
|
1217
|
+
function hasDependency(dependencies, dependencyName) {
|
|
1218
|
+
return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
|
|
1219
|
+
}
|
|
1220
|
+
function frameworkFromUserFacingValue(value, annotation) {
|
|
1221
|
+
switch (value.trim().toLowerCase()) {
|
|
1222
|
+
case "next":
|
|
1223
|
+
case "next.js":
|
|
1224
|
+
case "nextjs": return {
|
|
1225
|
+
key: "nextjs",
|
|
1226
|
+
buildType: "nextjs",
|
|
1227
|
+
displayName: "Next.js",
|
|
1228
|
+
annotation
|
|
1229
|
+
};
|
|
1230
|
+
case "hono": return {
|
|
1231
|
+
key: "hono",
|
|
1232
|
+
buildType: "bun",
|
|
1233
|
+
displayName: "Hono",
|
|
1234
|
+
annotation
|
|
1235
|
+
};
|
|
1236
|
+
case "tanstack":
|
|
1237
|
+
case "tanstack-start":
|
|
1238
|
+
case "@tanstack/start": return {
|
|
1239
|
+
key: "tanstack-start",
|
|
1240
|
+
buildType: "tanstack-start",
|
|
1241
|
+
displayName: "TanStack Start",
|
|
1242
|
+
annotation
|
|
1243
|
+
};
|
|
1244
|
+
default: throw frameworkNotDetectedError(void 0, value);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
function frameworkNotDetectedError(cwd, requestedFramework) {
|
|
1248
|
+
const supported = "Next.js, Hono, TanStack Start";
|
|
1249
|
+
const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
|
|
1250
|
+
return new CliError({
|
|
1251
|
+
code: "FRAMEWORK_NOT_DETECTED",
|
|
1252
|
+
domain: "app",
|
|
1253
|
+
summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
|
|
1254
|
+
why: `Supported Beta frameworks: ${supported}.`,
|
|
1255
|
+
fix: "Add one of these frameworks as a dependency, or pass --framework <nextjs|hono|tanstack-start>.",
|
|
1256
|
+
exitCode: 2,
|
|
1257
|
+
nextSteps: [
|
|
1258
|
+
"prisma-cli app deploy --framework nextjs",
|
|
1259
|
+
"prisma-cli app deploy --framework hono",
|
|
1260
|
+
"prisma-cli app deploy --framework tanstack-start"
|
|
1261
|
+
]
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
async function maybeRenderDeploySetupBlock(context, details) {
|
|
1265
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
1266
|
+
const directory = formatDeployDirectory(context.runtime.cwd);
|
|
1267
|
+
if (!details.firstDeploy) {
|
|
1268
|
+
context.output.stderr.write(`Deploying ${directory} to ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const title = `Setting up your local directory ${formatLocalDirectory(context.runtime.cwd, context.runtime.env)}`;
|
|
1272
|
+
const rows = details.firstDeploy ? [
|
|
1273
|
+
{
|
|
1274
|
+
label: "Workspace",
|
|
1275
|
+
value: details.workspaceName
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
label: "Project",
|
|
1279
|
+
value: details.projectName,
|
|
1280
|
+
origin: details.projectAnnotation
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
label: "Branch",
|
|
1284
|
+
value: details.branchName,
|
|
1285
|
+
origin: details.branchAnnotation
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
label: "App",
|
|
1289
|
+
value: details.appName,
|
|
1290
|
+
origin: details.appAnnotation
|
|
1291
|
+
},
|
|
1292
|
+
{
|
|
1293
|
+
label: "Framework",
|
|
1294
|
+
value: details.framework.displayName,
|
|
1295
|
+
origin: details.framework.annotation
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
label: "Runtime",
|
|
1299
|
+
value: `HTTP ${details.runtime.port}`,
|
|
1300
|
+
origin: details.runtime.annotation
|
|
1301
|
+
}
|
|
1302
|
+
] : [];
|
|
1303
|
+
const lines = [
|
|
1304
|
+
title,
|
|
1305
|
+
"",
|
|
1306
|
+
...renderDeployOutputRows(context.ui, rows),
|
|
1307
|
+
""
|
|
1308
|
+
];
|
|
1309
|
+
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
1310
|
+
}
|
|
1311
|
+
function maybeRenderLocalPinBound(context, projectName) {
|
|
1312
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
1313
|
+
context.output.stderr.write(`This directory is now linked to project ${projectName}.\n\n`);
|
|
1314
|
+
}
|
|
1315
|
+
async function maybeCustomizeDeploySettings(context, options) {
|
|
1316
|
+
if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitBuildType || options.explicitHttpPort || !canPrompt(context)) return {
|
|
1317
|
+
framework: options.framework,
|
|
1318
|
+
runtime: options.runtime
|
|
1319
|
+
};
|
|
1320
|
+
if (!await confirmPrompt({
|
|
1321
|
+
input: context.runtime.stdin,
|
|
1322
|
+
output: context.runtime.stderr,
|
|
1323
|
+
message: "Customize settings?",
|
|
1324
|
+
initialValue: false
|
|
1325
|
+
})) return {
|
|
1326
|
+
framework: options.framework,
|
|
1327
|
+
runtime: options.runtime
|
|
1328
|
+
};
|
|
1329
|
+
const framework = frameworkFromUserFacingValue(await selectPrompt({
|
|
1330
|
+
input: context.runtime.stdin,
|
|
1331
|
+
output: context.runtime.stderr,
|
|
1332
|
+
message: `Framework (${options.framework.displayName})`,
|
|
1333
|
+
choices: DEPLOY_FRAMEWORKS.map((framework) => ({
|
|
1334
|
+
label: frameworkDisplayName(framework),
|
|
1335
|
+
value: framework
|
|
1336
|
+
}))
|
|
1337
|
+
}), "set by you");
|
|
1338
|
+
const requestedPort = await textPrompt({
|
|
1339
|
+
input: context.runtime.stdin,
|
|
1340
|
+
output: context.runtime.stderr,
|
|
1341
|
+
message: `HTTP port (${options.runtime.port})`,
|
|
1342
|
+
placeholder: String(options.runtime.port),
|
|
1343
|
+
validate: validateDeployHttpPortText
|
|
1344
|
+
});
|
|
1345
|
+
const runtime = {
|
|
1346
|
+
port: requestedPort.trim() ? parseDeployHttpPort(requestedPort) : options.runtime.port,
|
|
1347
|
+
annotation: "set by you"
|
|
1348
|
+
};
|
|
1349
|
+
const changedRows = [framework.key !== options.framework.key ? {
|
|
1350
|
+
label: "Framework",
|
|
1351
|
+
value: framework.displayName,
|
|
1352
|
+
annotation: framework.annotation
|
|
1353
|
+
} : null, runtime.port !== options.runtime.port ? {
|
|
1354
|
+
label: "Runtime",
|
|
1355
|
+
value: `HTTP ${runtime.port}`,
|
|
1356
|
+
annotation: runtime.annotation
|
|
1357
|
+
} : null].filter((row) => Boolean(row));
|
|
1358
|
+
if (changedRows.length > 0 && !context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderDeployOutputRows(context.ui, changedRows.map((row) => ({
|
|
1359
|
+
label: row.label,
|
|
1360
|
+
value: row.value,
|
|
1361
|
+
origin: row.annotation
|
|
1362
|
+
}))).join("\n")}\n\n`);
|
|
1363
|
+
return {
|
|
1364
|
+
framework,
|
|
1365
|
+
runtime
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
function annotationForProjectResolution(resolution) {
|
|
1369
|
+
switch (resolution.projectSource) {
|
|
1370
|
+
case "explicit": return "set by --project";
|
|
1371
|
+
case "env": return `from ${PRISMA_PROJECT_ID_ENV_VAR}`;
|
|
1372
|
+
case "local-pin": return "from local pin";
|
|
1373
|
+
case "created": return resolution.targetNameSource === "directory-name" ? "created from directory name" : "created from package.json";
|
|
1374
|
+
case "package-name":
|
|
1375
|
+
case "directory-name": return "linked to existing project";
|
|
1376
|
+
case "platform-mapping":
|
|
1377
|
+
case "remembered-local": return "linked to existing project";
|
|
1378
|
+
case "prompt": return "selected by you";
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function frameworkDisplayName(framework) {
|
|
1382
|
+
switch (framework) {
|
|
1383
|
+
case "nextjs": return "Next.js";
|
|
1384
|
+
case "hono": return "Hono";
|
|
1385
|
+
case "tanstack-start": return "TanStack Start";
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function validateDeployHttpPortText(value) {
|
|
1389
|
+
if (!value?.trim()) return;
|
|
1390
|
+
try {
|
|
1391
|
+
parseDeployHttpPort(value);
|
|
1392
|
+
return;
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
return error instanceof CliError ? error.summary : String(error);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function formatDeployDirectory(cwd) {
|
|
1398
|
+
const basename = path.basename(cwd);
|
|
1399
|
+
return basename ? `./${basename}` : ".";
|
|
1400
|
+
}
|
|
1401
|
+
function formatLocalDirectory(cwd, env) {
|
|
1402
|
+
const resolved = path.resolve(cwd);
|
|
1403
|
+
const home = env.HOME ? path.resolve(env.HOME) : null;
|
|
1404
|
+
if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
|
|
1405
|
+
const relative = path.relative(home, resolved);
|
|
1406
|
+
return relative ? `~/${relative}` : "~";
|
|
1407
|
+
}
|
|
1408
|
+
return resolved;
|
|
859
1409
|
}
|
|
860
1410
|
async function readCurrentWorkspaceId(context) {
|
|
861
1411
|
const state = await context.stateStore.read();
|
|
@@ -891,9 +1441,12 @@ function parseLocalPort(requestedPort) {
|
|
|
891
1441
|
}
|
|
892
1442
|
function parseDeployPortMapping(requestedPort) {
|
|
893
1443
|
if (!requestedPort) return;
|
|
1444
|
+
return { http: parseDeployHttpPort(requestedPort) };
|
|
1445
|
+
}
|
|
1446
|
+
function parseDeployHttpPort(requestedPort) {
|
|
894
1447
|
const port = Number.parseInt(requestedPort, 10);
|
|
895
1448
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid HTTP port "${requestedPort}"`, "HTTP port must be an integer between 1 and 65535.", "Pass --http-port <number> with a valid port value.", ["prisma-cli app deploy --http-port 3000"], "app");
|
|
896
|
-
return
|
|
1449
|
+
return port;
|
|
897
1450
|
}
|
|
898
1451
|
function ensurePreviewAppMode(context) {
|
|
899
1452
|
if (isRealMode(context)) return;
|
|
@@ -911,6 +1464,83 @@ function deployFailedError(summary, error, nextSteps) {
|
|
|
911
1464
|
nextSteps
|
|
912
1465
|
});
|
|
913
1466
|
}
|
|
1467
|
+
function appDeployFailedError(error, progress) {
|
|
1468
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
1469
|
+
const debug = formatDebugDetails(error);
|
|
1470
|
+
if (progress.buildStarted && !progress.buildCompleted) return new CliError({
|
|
1471
|
+
code: "BUILD_FAILED",
|
|
1472
|
+
domain: "app",
|
|
1473
|
+
summary: "Build failed locally.",
|
|
1474
|
+
why,
|
|
1475
|
+
fix: "Inspect the build output above, fix the error, and redeploy.",
|
|
1476
|
+
debug,
|
|
1477
|
+
meta: { phase: "build" },
|
|
1478
|
+
humanLines: [
|
|
1479
|
+
"Build failed locally.",
|
|
1480
|
+
"",
|
|
1481
|
+
`✗ Built ${why}`,
|
|
1482
|
+
"",
|
|
1483
|
+
"Fix: Inspect the build output above, fix the error, and redeploy."
|
|
1484
|
+
],
|
|
1485
|
+
exitCode: 1,
|
|
1486
|
+
nextSteps: []
|
|
1487
|
+
});
|
|
1488
|
+
if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
|
|
1489
|
+
const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
|
|
1490
|
+
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."];
|
|
1491
|
+
const urlLines = progress.deploymentUrl ? [
|
|
1492
|
+
"",
|
|
1493
|
+
"URL",
|
|
1494
|
+
progress.deploymentUrl
|
|
1495
|
+
] : [];
|
|
1496
|
+
const humanLines = progress.containerLive ? [
|
|
1497
|
+
phaseHeadline,
|
|
1498
|
+
"",
|
|
1499
|
+
"This is usually a missing env var, a failed DB connection,",
|
|
1500
|
+
"or a crash on startup.",
|
|
1501
|
+
"",
|
|
1502
|
+
...recoveryLines,
|
|
1503
|
+
...urlLines
|
|
1504
|
+
] : [
|
|
1505
|
+
phaseHeadline,
|
|
1506
|
+
"",
|
|
1507
|
+
progress.uploadCompleted ? "The artifact uploaded, but the deployment did not start." : progress.archiveReady ? "The app built locally, but the artifact did not finish uploading." : "The app built locally, but the deployment did not start.",
|
|
1508
|
+
"",
|
|
1509
|
+
...recoveryLines
|
|
1510
|
+
];
|
|
1511
|
+
return new CliError({
|
|
1512
|
+
code: "DEPLOY_FAILED",
|
|
1513
|
+
domain: "app",
|
|
1514
|
+
summary: phaseHeadline,
|
|
1515
|
+
why,
|
|
1516
|
+
fix: progress.versionId ? `Inspect logs with prisma-cli app logs --deployment ${progress.versionId}.` : "Retry the command, or rerun with --trace for more detailed diagnostics.",
|
|
1517
|
+
debug,
|
|
1518
|
+
meta: {
|
|
1519
|
+
phase: progress.containerLive ? "runtime_ready" : "deploy",
|
|
1520
|
+
deploymentId: progress.versionId,
|
|
1521
|
+
deploymentUrl: progress.deploymentUrl
|
|
1522
|
+
},
|
|
1523
|
+
humanLines,
|
|
1524
|
+
exitCode: 1,
|
|
1525
|
+
nextSteps: []
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
function localResolutionPinStaleError() {
|
|
1529
|
+
return new CliError({
|
|
1530
|
+
code: "LOCAL_STATE_STALE",
|
|
1531
|
+
domain: "project",
|
|
1532
|
+
summary: "Local project binding is stale",
|
|
1533
|
+
why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
|
|
1534
|
+
fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} and re-run to re-bootstrap.`,
|
|
1535
|
+
meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
|
|
1536
|
+
exitCode: 1,
|
|
1537
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
function readDeployEnvOverride(context, name) {
|
|
1541
|
+
const value = context.runtime.env[name]?.trim();
|
|
1542
|
+
return value ? value : void 0;
|
|
1543
|
+
}
|
|
914
1544
|
/**
|
|
915
1545
|
* `app deploy` falls into "create a new project on first deploy" when no
|
|
916
1546
|
* existing project matches the package.json name (or the cwd basename as a
|
|
@@ -1032,6 +1662,9 @@ function isMissingProjectError(error) {
|
|
|
1032
1662
|
function findAppByName(apps, name) {
|
|
1033
1663
|
return apps.find((app) => app.name === name);
|
|
1034
1664
|
}
|
|
1665
|
+
function findAppsByName(apps, name) {
|
|
1666
|
+
return apps.filter((app) => app.name === name);
|
|
1667
|
+
}
|
|
1035
1668
|
function sortApps(apps) {
|
|
1036
1669
|
return apps.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
|
|
1037
1670
|
}
|