@prisma/cli 3.0.0-alpha.0 → 3.0.0-alpha.10
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/README.md +1 -16
- package/dist/adapters/git.js +49 -0
- package/dist/adapters/local-state.js +39 -1
- package/dist/cli2.js +60 -4
- package/dist/commands/app/index.js +43 -30
- package/dist/commands/auth/index.js +3 -2
- package/dist/commands/branch/index.js +2 -1
- package/dist/commands/env.js +87 -0
- package/dist/commands/git/index.js +36 -0
- package/dist/commands/project/index.js +12 -14
- package/dist/commands/version/index.js +18 -0
- package/dist/controllers/app-env.js +223 -0
- package/dist/controllers/app.js +1051 -173
- package/dist/controllers/auth.js +9 -9
- package/dist/controllers/branch.js +6 -6
- package/dist/controllers/project.js +451 -161
- package/dist/controllers/version.js +12 -0
- package/dist/lib/app/bun-project.js +1 -1
- package/dist/lib/app/deploy-output.js +15 -0
- package/dist/lib/app/env-config.js +57 -0
- package/dist/lib/app/env-vars.js +4 -4
- package/dist/lib/app/local-dev.js +2 -1
- package/dist/lib/app/preview-build.js +130 -144
- 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 +125 -24
- package/dist/lib/auth/auth-ops.js +58 -13
- package/dist/lib/auth/client.js +1 -1
- package/dist/lib/auth/guard.js +1 -1
- package/dist/lib/auth/login.js +115 -4
- package/dist/lib/project/local-pin.js +51 -0
- package/dist/lib/project/resolution.js +201 -0
- package/dist/lib/version.js +55 -0
- package/dist/output/patterns.js +15 -18
- package/dist/presenters/app-env.js +129 -0
- package/dist/presenters/app.js +16 -29
- package/dist/presenters/auth.js +2 -2
- package/dist/presenters/branch.js +6 -6
- package/dist/presenters/project.js +87 -44
- package/dist/presenters/version.js +29 -0
- package/dist/shell/command-meta.js +150 -84
- package/dist/shell/command-runner.js +32 -2
- package/dist/shell/errors.js +8 -3
- package/dist/shell/global-flags.js +13 -1
- package/dist/shell/help.js +8 -7
- package/dist/shell/output.js +29 -12
- package/dist/shell/prompt.js +12 -2
- package/dist/shell/runtime.js +1 -1
- package/dist/shell/ui.js +19 -1
- package/dist/use-cases/auth.js +9 -12
- package/dist/use-cases/branch.js +20 -20
- package/dist/use-cases/create-cli-gateways.js +3 -13
- package/dist/use-cases/project.js +2 -48
- package/package.json +3 -3
- package/dist/adapters/config.js +0 -74
package/dist/controllers/app.js
CHANGED
|
@@ -1,31 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
|
|
2
|
+
import { FileTokenStorage } from "../adapters/token-storage.js";
|
|
3
|
+
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
4
|
+
import { renderCommandHeader } from "../shell/ui.js";
|
|
5
|
+
import { writeJsonEvent } from "../shell/output.js";
|
|
3
6
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
|
-
import { textPrompt } from "../shell/prompt.js";
|
|
7
|
+
import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
|
|
5
8
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
9
|
+
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
6
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";
|
|
7
13
|
import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
|
|
8
|
-
import { projectNotFoundError } from "../
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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";
|
|
16
|
+
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
17
|
+
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
18
|
+
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
|
|
12
19
|
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
13
20
|
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
21
|
+
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
22
|
+
import { listRealWorkspaceProjects } from "./project.js";
|
|
23
|
+
import { access, readFile } from "node:fs/promises";
|
|
14
24
|
import path from "node:path";
|
|
15
25
|
import open from "open";
|
|
16
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";
|
|
17
35
|
function isRealMode(context) {
|
|
18
36
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
19
37
|
}
|
|
20
38
|
async function runAppBuild(context, entrypoint, requestedBuildType) {
|
|
21
39
|
const buildType = normalizeBuildType(requestedBuildType);
|
|
22
40
|
assertSupportedEntrypoint(buildType, entrypoint, "build");
|
|
23
|
-
const resolvedBuildType = await requireLocalBuildType(context, buildType, "build");
|
|
24
41
|
try {
|
|
25
42
|
const { artifact, buildType: actualBuildType } = await executePreviewBuild({
|
|
26
43
|
appPath: context.runtime.cwd,
|
|
27
44
|
entrypoint,
|
|
28
|
-
buildType
|
|
45
|
+
buildType
|
|
29
46
|
});
|
|
30
47
|
return {
|
|
31
48
|
command: "app.build",
|
|
@@ -35,14 +52,15 @@ async function runAppBuild(context, entrypoint, requestedBuildType) {
|
|
|
35
52
|
buildType: actualBuildType
|
|
36
53
|
},
|
|
37
54
|
warnings: [],
|
|
38
|
-
nextSteps: ["prisma app deploy"]
|
|
55
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
39
56
|
};
|
|
40
57
|
} catch (error) {
|
|
58
|
+
if (buildType === "auto" && isAutoBuildDetectionError(error)) throw usageError("App build requires an explicit framework when detection is ambiguous", `This preview auto-detects clear project shapes for ${RESOLVED_PREVIEW_BUILD_TYPES.map(formatBuildTypeName).join(", ")}.`, "Pass a supported --build-type value, or pass --entry <path> for a Bun app.", getBuildTypeExamples("build"), "app");
|
|
41
59
|
throw buildFailedError("Local app build failed", error);
|
|
42
60
|
}
|
|
43
61
|
}
|
|
44
62
|
async function runAppRun(context, entrypoint, requestedBuildType, requestedPort) {
|
|
45
|
-
if (context.flags.json) throw usageError("App run does not support --json", "This command streams the framework dev server directly and cannot return structured JSON.", "Rerun without --json to pass framework logs through directly.", ["prisma app run"], "app");
|
|
63
|
+
if (context.flags.json) throw usageError("App run does not support --json", "This command streams the framework dev server directly and cannot return structured JSON.", "Rerun without --json to pass framework logs through directly.", ["prisma-cli app run"], "app");
|
|
46
64
|
const buildType = normalizeBuildType(requestedBuildType);
|
|
47
65
|
assertSupportedEntrypoint(buildType, entrypoint, "run");
|
|
48
66
|
const port = parseLocalPort(requestedPort);
|
|
@@ -75,16 +93,75 @@ async function runAppRun(context, entrypoint, requestedBuildType, requestedPort)
|
|
|
75
93
|
}
|
|
76
94
|
async function runAppDeploy(context, appName, options) {
|
|
77
95
|
ensurePreviewAppMode(context);
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
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");
|
|
81
111
|
const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
|
|
82
|
-
const
|
|
83
|
-
const projectId = await
|
|
84
|
-
|
|
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();
|
|
85
161
|
const deployResult = await provider.deployApp({
|
|
86
162
|
cwd: context.runtime.cwd,
|
|
87
163
|
projectId,
|
|
164
|
+
branchName: target.branch.name,
|
|
88
165
|
appId: selectedApp.appId,
|
|
89
166
|
appName: selectedApp.appName,
|
|
90
167
|
region: selectedApp.region,
|
|
@@ -92,11 +169,12 @@ async function runAppDeploy(context, appName, options) {
|
|
|
92
169
|
buildType,
|
|
93
170
|
portMapping,
|
|
94
171
|
envVars,
|
|
95
|
-
interaction:
|
|
96
|
-
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)
|
|
97
174
|
}).catch((error) => {
|
|
98
|
-
throw
|
|
175
|
+
throw appDeployFailedError(error, progressState);
|
|
99
176
|
});
|
|
177
|
+
const deployDurationMs = Date.now() - deployStartedAt;
|
|
100
178
|
await context.stateStore.setSelectedApp(projectId, {
|
|
101
179
|
id: deployResult.app.id,
|
|
102
180
|
name: deployResult.app.name
|
|
@@ -105,29 +183,37 @@ async function runAppDeploy(context, appName, options) {
|
|
|
105
183
|
return {
|
|
106
184
|
command: "app.deploy",
|
|
107
185
|
result: {
|
|
108
|
-
|
|
186
|
+
workspace: target.workspace,
|
|
187
|
+
project: target.project,
|
|
188
|
+
branch: target.branch,
|
|
189
|
+
resolution: target.resolution,
|
|
109
190
|
app: {
|
|
110
191
|
id: deployResult.app.id,
|
|
111
192
|
name: deployResult.app.name
|
|
112
193
|
},
|
|
113
|
-
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
|
|
114
200
|
},
|
|
115
201
|
warnings: [],
|
|
116
|
-
nextSteps: ["prisma app list-deploys", `prisma app show-deploy ${deployResult.deployment.id}`]
|
|
202
|
+
nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
|
|
117
203
|
};
|
|
118
204
|
}
|
|
119
|
-
async function runAppUpdateEnv(context, appName, envAssignments) {
|
|
205
|
+
async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
|
|
120
206
|
ensurePreviewAppMode(context);
|
|
207
|
+
emitLegacyEnvDeprecationWarning(context, "app update-env", "project env add");
|
|
121
208
|
const envVars = parseEnvAssignments(envAssignments, {
|
|
122
209
|
commandName: "update-env",
|
|
123
210
|
requireAtLeastOne: true
|
|
124
211
|
});
|
|
125
|
-
const projectId = await
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The linked project does not have any deployed app yet.");
|
|
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);
|
|
214
|
+
if (!selectedApp) throw noDeploymentsError("No deployments available to update environment variables", "The resolved project does not have any deployed app yet.");
|
|
129
215
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
130
|
-
throw deployFailedError("Failed to inspect app deployments", error, ["prisma app list-deploys"]);
|
|
216
|
+
throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
|
|
131
217
|
});
|
|
132
218
|
if (deploymentsResult.deployments.length === 0) throw noDeploymentsError("No deployments available to update environment variables", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
|
|
133
219
|
const updateResult = await provider.updateAppEnv({
|
|
@@ -136,7 +222,7 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
|
|
|
136
222
|
progress: createPreviewUpdateEnvProgress(context.output.stderr, !context.flags.json && !context.flags.quiet),
|
|
137
223
|
promoteProgress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
|
|
138
224
|
}).catch((error) => {
|
|
139
|
-
throw deployFailedError("Failed to update app environment variables", error, ["prisma app list-env"]);
|
|
225
|
+
throw deployFailedError("Failed to update app environment variables", error, ["prisma-cli app list-env"]);
|
|
140
226
|
});
|
|
141
227
|
await context.stateStore.setSelectedApp(projectId, {
|
|
142
228
|
id: updateResult.app.id,
|
|
@@ -155,14 +241,14 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
|
|
|
155
241
|
variables: updateResult.variables
|
|
156
242
|
},
|
|
157
243
|
warnings: [],
|
|
158
|
-
nextSteps: ["prisma app list-env", `prisma app show-deploy ${updateResult.deployment.id}`]
|
|
244
|
+
nextSteps: ["prisma-cli app list-env", `prisma-cli app show-deploy ${updateResult.deployment.id}`]
|
|
159
245
|
};
|
|
160
246
|
}
|
|
161
|
-
async function runAppListEnv(context, appName) {
|
|
247
|
+
async function runAppListEnv(context, appName, projectRef) {
|
|
162
248
|
ensurePreviewAppMode(context);
|
|
163
|
-
|
|
164
|
-
const provider = await
|
|
165
|
-
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
249
|
+
emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
|
|
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);
|
|
166
252
|
if (!selectedApp) return {
|
|
167
253
|
command: "app.list-env",
|
|
168
254
|
result: {
|
|
@@ -172,10 +258,10 @@ async function runAppListEnv(context, appName) {
|
|
|
172
258
|
variables: []
|
|
173
259
|
},
|
|
174
260
|
warnings: [],
|
|
175
|
-
nextSteps: ["prisma app deploy"]
|
|
261
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
176
262
|
};
|
|
177
263
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
178
|
-
throw deployFailedError("Failed to inspect app deployments", error, ["prisma app list-deploys"]);
|
|
264
|
+
throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
|
|
179
265
|
});
|
|
180
266
|
const knownLiveDeploymentId = await context.stateStore.readKnownLiveDeployment(projectId, deploymentsResult.app.id);
|
|
181
267
|
const missingKnownLiveDeploymentId = knownLiveDeploymentId && !deploymentsResult.deployments.some((candidate) => candidate.id === knownLiveDeploymentId) ? knownLiveDeploymentId : null;
|
|
@@ -191,7 +277,7 @@ async function runAppListEnv(context, appName) {
|
|
|
191
277
|
appId: deploymentsResult.app.id,
|
|
192
278
|
deploymentId: missingKnownLiveDeploymentId
|
|
193
279
|
}).catch((error) => {
|
|
194
|
-
throw deployFailedError("Failed to inspect app environment variables", error, ["prisma app list-deploys"]);
|
|
280
|
+
throw deployFailedError("Failed to inspect app environment variables", error, ["prisma-cli app list-deploys"]);
|
|
195
281
|
});
|
|
196
282
|
return {
|
|
197
283
|
command: "app.list-env",
|
|
@@ -205,7 +291,7 @@ async function runAppListEnv(context, appName) {
|
|
|
205
291
|
variables: envResult.variables
|
|
206
292
|
},
|
|
207
293
|
warnings: [],
|
|
208
|
-
nextSteps: [`prisma app show-deploy ${envResult.deployment.id}`]
|
|
294
|
+
nextSteps: [`prisma-cli app show-deploy ${envResult.deployment.id}`]
|
|
209
295
|
};
|
|
210
296
|
}
|
|
211
297
|
if (!deployment) return {
|
|
@@ -220,13 +306,13 @@ async function runAppListEnv(context, appName) {
|
|
|
220
306
|
variables: []
|
|
221
307
|
},
|
|
222
308
|
warnings: [],
|
|
223
|
-
nextSteps: ["prisma app deploy"]
|
|
309
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
224
310
|
};
|
|
225
311
|
const envResult = await provider.listAppEnvNames({
|
|
226
312
|
appId: deploymentsResult.app.id,
|
|
227
313
|
deploymentId: deployment.id
|
|
228
314
|
}).catch((error) => {
|
|
229
|
-
throw deployFailedError("Failed to inspect app environment variables", error, ["prisma app list-deploys"]);
|
|
315
|
+
throw deployFailedError("Failed to inspect app environment variables", error, ["prisma-cli app list-deploys"]);
|
|
230
316
|
});
|
|
231
317
|
return {
|
|
232
318
|
command: "app.list-env",
|
|
@@ -243,14 +329,13 @@ async function runAppListEnv(context, appName) {
|
|
|
243
329
|
variables: envResult.variables
|
|
244
330
|
},
|
|
245
331
|
warnings: [],
|
|
246
|
-
nextSteps: deployment.id ? [`prisma app show-deploy ${deployment.id}`] : ["prisma app deploy"]
|
|
332
|
+
nextSteps: deployment.id ? [`prisma-cli app show-deploy ${deployment.id}`] : ["prisma-cli app deploy"]
|
|
247
333
|
};
|
|
248
334
|
}
|
|
249
|
-
async function runAppListDeploys(context, appName) {
|
|
335
|
+
async function runAppListDeploys(context, appName, projectRef) {
|
|
250
336
|
ensurePreviewAppMode(context);
|
|
251
|
-
const projectId = await
|
|
252
|
-
const
|
|
253
|
-
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);
|
|
254
339
|
if (!selectedApp) return {
|
|
255
340
|
command: "app.list-deploys",
|
|
256
341
|
result: {
|
|
@@ -259,10 +344,10 @@ async function runAppListDeploys(context, appName) {
|
|
|
259
344
|
deployments: []
|
|
260
345
|
},
|
|
261
346
|
warnings: [],
|
|
262
|
-
nextSteps: ["prisma app deploy"]
|
|
347
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
263
348
|
};
|
|
264
349
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
265
|
-
throw deployFailedError("Failed to list app deployments", error, ["prisma app deploy"]);
|
|
350
|
+
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app deploy"]);
|
|
266
351
|
});
|
|
267
352
|
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
268
353
|
const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId).slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
|
|
@@ -281,14 +366,13 @@ async function runAppListDeploys(context, appName) {
|
|
|
281
366
|
deployments
|
|
282
367
|
},
|
|
283
368
|
warnings: [],
|
|
284
|
-
nextSteps: deployments.length > 0 ? [`prisma app show-deploy ${deployments[0]?.id}`] : ["prisma app deploy"]
|
|
369
|
+
nextSteps: deployments.length > 0 ? [`prisma-cli app show-deploy ${deployments[0]?.id}`] : ["prisma-cli app deploy"]
|
|
285
370
|
};
|
|
286
371
|
}
|
|
287
|
-
async function runAppShow(context, appName) {
|
|
372
|
+
async function runAppShow(context, appName, projectRef) {
|
|
288
373
|
ensurePreviewAppMode(context);
|
|
289
|
-
const projectId = await
|
|
290
|
-
const
|
|
291
|
-
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);
|
|
292
376
|
if (!selectedApp) return {
|
|
293
377
|
command: "app.show",
|
|
294
378
|
result: {
|
|
@@ -299,10 +383,10 @@ async function runAppShow(context, appName) {
|
|
|
299
383
|
recentDeployments: []
|
|
300
384
|
},
|
|
301
385
|
warnings: [],
|
|
302
|
-
nextSteps: ["prisma app deploy"]
|
|
386
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
303
387
|
};
|
|
304
388
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
305
|
-
throw deployFailedError("Failed to inspect app", error, ["prisma app list-deploys"]);
|
|
389
|
+
throw deployFailedError("Failed to inspect app", error, ["prisma-cli app list-deploys"]);
|
|
306
390
|
});
|
|
307
391
|
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
308
392
|
const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId).slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
|
|
@@ -330,19 +414,20 @@ async function runAppShow(context, appName) {
|
|
|
330
414
|
async function runAppShowDeploy(context, deploymentId) {
|
|
331
415
|
ensurePreviewAppMode(context);
|
|
332
416
|
const deployment = await (await requirePreviewAppProvider(context)).showDeployment(deploymentId).catch((error) => {
|
|
333
|
-
throw deployFailedError("Failed to show deployment", error, ["prisma app list-deploys"]);
|
|
417
|
+
throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
|
|
334
418
|
});
|
|
335
419
|
if (!deployment) throw new CliError({
|
|
336
420
|
code: "DEPLOYMENT_NOT_FOUND",
|
|
337
421
|
domain: "app",
|
|
338
422
|
summary: `Deployment "${deploymentId}" not found`,
|
|
339
423
|
why: "The requested deployment does not exist or is no longer available.",
|
|
340
|
-
fix: "Run prisma app list-deploys to choose an available deployment id.",
|
|
424
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id.",
|
|
341
425
|
exitCode: 1,
|
|
342
|
-
nextSteps: ["prisma app list-deploys"]
|
|
426
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
343
427
|
});
|
|
344
|
-
const
|
|
345
|
-
const
|
|
428
|
+
const workspaceId = deployment?.app ? await readCurrentWorkspaceId(context) : null;
|
|
429
|
+
const rememberedProject = workspaceId ? await context.stateStore.readRememberedProject(workspaceId) : null;
|
|
430
|
+
const knownLiveDeploymentId = deployment?.app && rememberedProject ? await context.stateStore.readKnownLiveDeployment(rememberedProject.id, deployment.app.id) : null;
|
|
346
431
|
const providerLiveDeploymentId = deployment.app?.liveDeploymentId ?? null;
|
|
347
432
|
return {
|
|
348
433
|
command: "app.show-deploy",
|
|
@@ -360,14 +445,13 @@ async function runAppShowDeploy(context, deploymentId) {
|
|
|
360
445
|
nextSteps: []
|
|
361
446
|
};
|
|
362
447
|
}
|
|
363
|
-
async function runAppOpen(context, appName) {
|
|
448
|
+
async function runAppOpen(context, appName, projectRef) {
|
|
364
449
|
ensurePreviewAppMode(context);
|
|
365
|
-
const projectId = await
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The linked project does not have any deployed app yet.");
|
|
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);
|
|
452
|
+
if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
|
|
369
453
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
370
|
-
throw deployFailedError("Failed to resolve app URL", error, ["prisma app show"]);
|
|
454
|
+
throw deployFailedError("Failed to resolve app URL", error, ["prisma-cli app show"]);
|
|
371
455
|
});
|
|
372
456
|
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
373
457
|
const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId).slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
|
|
@@ -377,7 +461,7 @@ async function runAppOpen(context, appName) {
|
|
|
377
461
|
name: deploymentsResult.app.name
|
|
378
462
|
});
|
|
379
463
|
if (!liveDeployment) throw noDeploymentsError("No deployments available to open", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
|
|
380
|
-
if (!deploymentsResult.app.liveUrl) throw featureUnavailableError("Live URL is not available for the selected app", "Deployments exist, but the provider does not expose a stable live service URL for this app yet.", "Run prisma app show to inspect the current deployment state and try again after the app reports a live URL.", ["prisma app show"], "app");
|
|
464
|
+
if (!deploymentsResult.app.liveUrl) throw featureUnavailableError("Live URL is not available for the selected app", "Deployments exist, but the provider does not expose a stable live service URL for this app yet.", "Run prisma-cli app show to inspect the current deployment state and try again after the app reports a live URL.", ["prisma-cli app show"], "app");
|
|
381
465
|
const shouldOpen = canPrompt(context);
|
|
382
466
|
if (shouldOpen) await open(deploymentsResult.app.liveUrl);
|
|
383
467
|
return {
|
|
@@ -392,20 +476,139 @@ async function runAppOpen(context, appName) {
|
|
|
392
476
|
opened: shouldOpen
|
|
393
477
|
},
|
|
394
478
|
warnings: [],
|
|
395
|
-
nextSteps: ["prisma app show", `prisma app show-deploy ${liveDeployment.id}`]
|
|
479
|
+
nextSteps: ["prisma-cli app show", `prisma-cli app show-deploy ${liveDeployment.id}`]
|
|
396
480
|
};
|
|
397
481
|
}
|
|
398
|
-
async function runAppLogs(context,
|
|
482
|
+
async function runAppLogs(context, appName, deploymentId, projectRef) {
|
|
399
483
|
ensurePreviewAppMode(context);
|
|
400
|
-
|
|
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);
|
|
486
|
+
if (!context.flags.json && !context.flags.quiet) {
|
|
487
|
+
const lines = renderCommandHeader(context.ui, {
|
|
488
|
+
commandLabel: "app logs",
|
|
489
|
+
description: "Streaming logs for the selected deployment.",
|
|
490
|
+
docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
|
|
491
|
+
rows: [
|
|
492
|
+
{
|
|
493
|
+
key: "project",
|
|
494
|
+
value: projectId
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
key: "app",
|
|
498
|
+
value: target.app.name
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
key: "deployment",
|
|
502
|
+
value: target.deployment.id
|
|
503
|
+
}
|
|
504
|
+
]
|
|
505
|
+
});
|
|
506
|
+
if (lines.length > 0) context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
507
|
+
}
|
|
508
|
+
await provider.streamDeploymentLogs({
|
|
509
|
+
deploymentId: target.deployment.id,
|
|
510
|
+
onRecord: (record) => writeLogRecord(context, record)
|
|
511
|
+
}).catch((error) => {
|
|
512
|
+
throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
async function resolveExplicitLogDeployment(context, provider, projectId, branchName, appName, deploymentId) {
|
|
516
|
+
if (appName) {
|
|
517
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
|
|
518
|
+
if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
|
|
519
|
+
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
520
|
+
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
521
|
+
});
|
|
522
|
+
const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
|
|
523
|
+
await context.stateStore.setSelectedApp(projectId, {
|
|
524
|
+
id: deploymentsResult.app.id,
|
|
525
|
+
name: deploymentsResult.app.name
|
|
526
|
+
});
|
|
527
|
+
return {
|
|
528
|
+
app: deploymentsResult.app,
|
|
529
|
+
deployment
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
const shown = await provider.showDeployment(deploymentId).catch((error) => {
|
|
533
|
+
throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
|
|
534
|
+
});
|
|
535
|
+
if (!shown) throw new CliError({
|
|
536
|
+
code: "DEPLOYMENT_NOT_FOUND",
|
|
537
|
+
domain: "app",
|
|
538
|
+
summary: `Deployment "${deploymentId}" not found`,
|
|
539
|
+
why: "The requested deployment does not exist or is no longer available.",
|
|
540
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id.",
|
|
541
|
+
exitCode: 1,
|
|
542
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
543
|
+
});
|
|
544
|
+
if (!shown.app) throw new CliError({
|
|
545
|
+
code: "DEPLOYMENT_NOT_FOUND",
|
|
546
|
+
domain: "app",
|
|
547
|
+
summary: `Deployment "${deploymentId}" is not attached to an app`,
|
|
548
|
+
why: "The requested deployment could be found, but its app could not be resolved.",
|
|
549
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id for the selected app.",
|
|
550
|
+
exitCode: 1,
|
|
551
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
552
|
+
});
|
|
553
|
+
const resolvedProjectApp = (await listApps(context, provider, projectId, branchName)).find((app) => app.id === shown.app?.id);
|
|
554
|
+
if (!resolvedProjectApp) throw new CliError({
|
|
555
|
+
code: "DEPLOYMENT_NOT_FOUND",
|
|
556
|
+
domain: "app",
|
|
557
|
+
summary: `Deployment "${deploymentId}" not found in the resolved project`,
|
|
558
|
+
why: "The requested deployment does not belong to an app in the resolved project.",
|
|
559
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id for this project.",
|
|
560
|
+
exitCode: 1,
|
|
561
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
562
|
+
});
|
|
563
|
+
await context.stateStore.setSelectedApp(projectId, {
|
|
564
|
+
id: resolvedProjectApp.id,
|
|
565
|
+
name: resolvedProjectApp.name
|
|
566
|
+
});
|
|
567
|
+
return {
|
|
568
|
+
app: resolvedProjectApp,
|
|
569
|
+
deployment: shown.deployment
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
async function resolveLiveLogDeployment(context, provider, projectId, branchName, appName) {
|
|
573
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, branchName), appName);
|
|
574
|
+
if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The resolved project does not have any deployed app yet.");
|
|
575
|
+
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
576
|
+
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
577
|
+
});
|
|
578
|
+
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
579
|
+
const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId);
|
|
580
|
+
const deployment = currentLiveDeploymentId ? deployments.find((candidate) => candidate.id === currentLiveDeploymentId) ?? null : null;
|
|
581
|
+
await context.stateStore.setSelectedApp(projectId, {
|
|
582
|
+
id: deploymentsResult.app.id,
|
|
583
|
+
name: deploymentsResult.app.name
|
|
584
|
+
});
|
|
585
|
+
if (!deployment) throw noDeploymentsError("No deployments available to stream logs", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
|
|
586
|
+
return {
|
|
587
|
+
app: deploymentsResult.app,
|
|
588
|
+
deployment
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function writeLogRecord(context, record) {
|
|
592
|
+
if (context.flags.json) {
|
|
593
|
+
writeJsonEvent(context.output, {
|
|
594
|
+
type: record.type,
|
|
595
|
+
command: "app.logs",
|
|
596
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
597
|
+
data: record
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (record.type === "log") {
|
|
602
|
+
context.output.stdout.write(record.text);
|
|
603
|
+
if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
|
|
604
|
+
}
|
|
401
605
|
}
|
|
402
|
-
async function runAppPromote(context, deploymentId, appName) {
|
|
606
|
+
async function runAppPromote(context, deploymentId, appName, projectRef) {
|
|
403
607
|
ensurePreviewAppMode(context);
|
|
404
|
-
const projectId = await
|
|
405
|
-
const
|
|
406
|
-
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");
|
|
407
610
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
408
|
-
throw deployFailedError("Failed to list app deployments", error, ["prisma app list-deploys"]);
|
|
611
|
+
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
409
612
|
});
|
|
410
613
|
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
411
614
|
const targetDeployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
|
|
@@ -419,7 +622,7 @@ async function runAppPromote(context, deploymentId, appName) {
|
|
|
419
622
|
deploymentId: targetDeployment.id,
|
|
420
623
|
progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
|
|
421
624
|
}).catch((error) => {
|
|
422
|
-
throw deployFailedError("Failed to promote deployment", error, ["prisma app list-deploys"]);
|
|
625
|
+
throw deployFailedError("Failed to promote deployment", error, ["prisma-cli app list-deploys"]);
|
|
423
626
|
});
|
|
424
627
|
await context.stateStore.setKnownLiveDeployment(projectId, deploymentsResult.app.id, targetDeployment.id);
|
|
425
628
|
return {
|
|
@@ -437,16 +640,15 @@ async function runAppPromote(context, deploymentId, appName) {
|
|
|
437
640
|
}
|
|
438
641
|
},
|
|
439
642
|
warnings: targetAlreadyLive ? ["The selected deployment is already live for this app."] : [],
|
|
440
|
-
nextSteps: ["prisma app list-deploys", `prisma app show-deploy ${targetDeployment.id}`]
|
|
643
|
+
nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
|
|
441
644
|
};
|
|
442
645
|
}
|
|
443
|
-
async function runAppRollback(context, appName, deploymentId) {
|
|
646
|
+
async function runAppRollback(context, appName, deploymentId, projectRef) {
|
|
444
647
|
ensurePreviewAppMode(context);
|
|
445
|
-
const projectId = await
|
|
446
|
-
const
|
|
447
|
-
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");
|
|
448
650
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
449
|
-
throw deployFailedError("Failed to list app deployments", error, ["prisma app list-deploys"]);
|
|
651
|
+
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
450
652
|
});
|
|
451
653
|
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
452
654
|
const currentLiveDeployment = currentLiveDeploymentId ? deploymentsResult.deployments.find((deployment) => deployment.id === currentLiveDeploymentId) ?? null : null;
|
|
@@ -461,7 +663,7 @@ async function runAppRollback(context, appName, deploymentId) {
|
|
|
461
663
|
deploymentId: targetDeployment.id,
|
|
462
664
|
progress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
|
|
463
665
|
}).catch((error) => {
|
|
464
|
-
throw deployFailedError("Failed to roll back deployment", error, ["prisma app list-deploys"]);
|
|
666
|
+
throw deployFailedError("Failed to roll back deployment", error, ["prisma-cli app list-deploys"]);
|
|
465
667
|
});
|
|
466
668
|
await context.stateStore.setKnownLiveDeployment(projectId, deploymentsResult.app.id, targetDeployment.id);
|
|
467
669
|
return {
|
|
@@ -480,17 +682,16 @@ async function runAppRollback(context, appName, deploymentId) {
|
|
|
480
682
|
previousLiveDeploymentId: currentLiveDeployment?.id ?? null
|
|
481
683
|
},
|
|
482
684
|
warnings: targetAlreadyLive ? ["The selected deployment is already live for this app."] : [],
|
|
483
|
-
nextSteps: ["prisma app list-deploys", `prisma app show-deploy ${targetDeployment.id}`]
|
|
685
|
+
nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${targetDeployment.id}`]
|
|
484
686
|
};
|
|
485
687
|
}
|
|
486
|
-
async function runAppRemove(context, appName) {
|
|
688
|
+
async function runAppRemove(context, appName, projectRef) {
|
|
487
689
|
ensurePreviewAppMode(context);
|
|
488
|
-
const projectId = await
|
|
489
|
-
const
|
|
490
|
-
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");
|
|
491
692
|
await confirmAppRemoval(context, selectedApp);
|
|
492
693
|
const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
|
|
493
|
-
throw removeFailedError("Failed to remove app", error, ["prisma app show", "prisma app list-deploys"]);
|
|
694
|
+
throw removeFailedError("Failed to remove app", error, ["prisma-cli app show", "prisma-cli app list-deploys"]);
|
|
494
695
|
});
|
|
495
696
|
const warnings = await cleanupRemovedAppState(context, projectId, removedApp.id);
|
|
496
697
|
return {
|
|
@@ -504,48 +705,122 @@ async function runAppRemove(context, appName) {
|
|
|
504
705
|
removed: true
|
|
505
706
|
},
|
|
506
707
|
warnings,
|
|
507
|
-
nextSteps: ["prisma app deploy", "prisma app list-deploys"]
|
|
708
|
+
nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
|
|
508
709
|
};
|
|
509
710
|
}
|
|
510
|
-
async function
|
|
511
|
-
if (explicitAppName) {
|
|
512
|
-
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];
|
|
513
716
|
if (matched) return {
|
|
514
717
|
appId: matched.id,
|
|
515
|
-
|
|
718
|
+
displayName: matched.name,
|
|
719
|
+
annotation: "set by --app",
|
|
720
|
+
firstDeploy: options.firstDeploy
|
|
516
721
|
};
|
|
517
722
|
return {
|
|
518
|
-
appName: explicitAppName,
|
|
723
|
+
appName: options.explicitAppName,
|
|
519
724
|
region: PREVIEW_DEFAULT_REGION,
|
|
520
|
-
|
|
725
|
+
displayName: options.explicitAppName,
|
|
726
|
+
annotation: "set by --app",
|
|
727
|
+
firstDeploy: options.firstDeploy
|
|
521
728
|
};
|
|
522
729
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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 {
|
|
527
734
|
appId: matched.id,
|
|
528
|
-
|
|
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
|
|
529
794
|
};
|
|
530
|
-
if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the linked project.", "Pass --app <name>, or rerun prisma app deploy in a TTY to choose or create an app again.", ["prisma app deploy"], "app");
|
|
531
795
|
}
|
|
532
|
-
|
|
533
|
-
|
|
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
|
+
});
|
|
534
809
|
}
|
|
535
810
|
async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
|
|
536
811
|
if (explicitAppName) {
|
|
537
812
|
const matched = findAppByName(apps, explicitAppName);
|
|
538
|
-
if (!matched) throw usageError("Selected app does not exist in the
|
|
813
|
+
if (!matched) throw usageError("Selected app does not exist in the resolved project", `The app "${explicitAppName}" could not be found in resolved project "${projectId}".`, "Pass the name of an existing app, or rerun prisma-cli app list-deploys in a TTY to choose one.", ["prisma-cli app list-deploys"], "app");
|
|
539
814
|
return matched;
|
|
540
815
|
}
|
|
541
816
|
const savedSelection = await context.stateStore.readSelectedApp(projectId);
|
|
542
817
|
if (savedSelection) {
|
|
543
818
|
const matched = apps.find((app) => app.id === savedSelection.id) ?? findAppByName(apps, savedSelection.name);
|
|
544
819
|
if (matched) return matched;
|
|
545
|
-
if (!canPrompt(context)) throw usageError("Saved app selection is no longer available", "The locally selected app could not be found in the
|
|
820
|
+
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 list-deploys in a TTY to choose an available app.", ["prisma-cli app list-deploys"], "app");
|
|
546
821
|
}
|
|
547
822
|
if (apps.length === 0) return null;
|
|
548
|
-
if (!canPrompt(context)) throw usageError("App selection required in non-interactive mode", "This command cannot choose an app in the current mode.", "Pass --app <name>, or rerun prisma app list-deploys in a TTY to choose an app.", ["prisma app list-deploys"], "app");
|
|
823
|
+
if (!canPrompt(context)) throw usageError("App selection required in non-interactive mode", "This command cannot choose an app in the current mode.", "Pass --app <name>, or rerun prisma-cli app list-deploys in a TTY to choose an app.", ["prisma-cli app list-deploys"], "app");
|
|
549
824
|
const selectedId = await createSelectPromptPort(context).select({
|
|
550
825
|
message: "Select an app",
|
|
551
826
|
choices: sortApps(apps).map((app) => ({
|
|
@@ -558,7 +833,7 @@ async function resolveExistingAppSelection(context, projectId, apps, explicitApp
|
|
|
558
833
|
async function requireReleaseAppSelection(context, projectId, apps, explicitAppName, commandName) {
|
|
559
834
|
const selectedApp = await resolveExistingAppSelection(context, projectId, apps, explicitAppName);
|
|
560
835
|
if (selectedApp) return selectedApp;
|
|
561
|
-
throw usageError(`App ${commandName} requires an existing app`, "The
|
|
836
|
+
throw usageError(`App ${commandName} requires an existing app`, "The resolved project does not have an app that can be selected for this command.", `Deploy an app first, or rerun prisma-cli app ${commandName} with --app <name> after an app exists.`, ["prisma-cli app deploy", "prisma-cli app list-deploys"], "app");
|
|
562
837
|
}
|
|
563
838
|
async function confirmAppRemoval(context, app) {
|
|
564
839
|
if (context.flags.yes) return;
|
|
@@ -567,9 +842,9 @@ async function confirmAppRemoval(context, app) {
|
|
|
567
842
|
domain: "app",
|
|
568
843
|
summary: "App remove requires confirmation in the current mode",
|
|
569
844
|
why: "This command is destructive and cannot prompt for confirmation in the current mode.",
|
|
570
|
-
fix: `Pass --yes to confirm removal of "${app.name}", or rerun prisma app remove in an interactive TTY.`,
|
|
845
|
+
fix: `Pass --yes to confirm removal of "${app.name}", or rerun prisma-cli app remove in an interactive TTY.`,
|
|
571
846
|
exitCode: 1,
|
|
572
|
-
nextSteps: [`prisma app remove --app ${app.name} --yes`]
|
|
847
|
+
nextSteps: [`prisma-cli app remove --app ${app.name} --yes`]
|
|
573
848
|
});
|
|
574
849
|
await textPrompt({
|
|
575
850
|
input: context.runtime.stdin,
|
|
@@ -601,9 +876,9 @@ function requireDeploymentForApp(deployments, deploymentId, appName) {
|
|
|
601
876
|
domain: "app",
|
|
602
877
|
summary: `Deployment "${deploymentId}" not found for app "${appName}"`,
|
|
603
878
|
why: "The requested deployment does not belong to the resolved app or is no longer available.",
|
|
604
|
-
fix: "Run prisma app list-deploys to choose an available deployment id for this app.",
|
|
879
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id for this app.",
|
|
605
880
|
exitCode: 1,
|
|
606
|
-
nextSteps: ["prisma app list-deploys"]
|
|
881
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
607
882
|
});
|
|
608
883
|
}
|
|
609
884
|
async function resolveCurrentLiveDeploymentId(context, projectId, app, deployments) {
|
|
@@ -616,10 +891,10 @@ async function resolveCurrentLiveDeploymentId(context, projectId, app, deploymen
|
|
|
616
891
|
}
|
|
617
892
|
function buildAppShowNextSteps(liveUrl, liveDeployment, deployments) {
|
|
618
893
|
const nextSteps = [];
|
|
619
|
-
if (liveUrl) nextSteps.push("prisma app open");
|
|
620
|
-
if (liveDeployment) nextSteps.push(`prisma app show-deploy ${liveDeployment.id}`);
|
|
621
|
-
else if (deployments[0]) nextSteps.push(`prisma app show-deploy ${deployments[0].id}`);
|
|
622
|
-
else nextSteps.push("prisma app deploy");
|
|
894
|
+
if (liveUrl) nextSteps.push("prisma-cli app open");
|
|
895
|
+
if (liveDeployment) nextSteps.push(`prisma-cli app show-deploy ${liveDeployment.id}`);
|
|
896
|
+
else if (deployments[0]) nextSteps.push(`prisma-cli app show-deploy ${deployments[0].id}`);
|
|
897
|
+
else nextSteps.push("prisma-cli app deploy");
|
|
623
898
|
return nextSteps;
|
|
624
899
|
}
|
|
625
900
|
function applyLiveDeploymentHint(deployments, currentLiveDeploymentId) {
|
|
@@ -640,94 +915,542 @@ function resolveRollbackTarget(deployments, currentLiveDeploymentId) {
|
|
|
640
915
|
domain: "app",
|
|
641
916
|
summary: "No previous deployment available for rollback",
|
|
642
917
|
why: "The selected app does not have an earlier deployment to switch back to.",
|
|
643
|
-
fix: "Deploy a second version first, or rerun prisma app rollback --to <deployment-id> for a specific earlier deployment.",
|
|
918
|
+
fix: "Deploy a second version first, or rerun prisma-cli app rollback --to <deployment-id> for a specific earlier deployment.",
|
|
644
919
|
exitCode: 1,
|
|
645
|
-
nextSteps: ["prisma app deploy", "prisma app list-deploys"]
|
|
920
|
+
nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
|
|
646
921
|
});
|
|
647
922
|
}
|
|
648
|
-
async function listApps(context, provider, projectId) {
|
|
649
|
-
return provider.listApps(projectId).then(sortApps).catch((error) => {
|
|
650
|
-
if (isMissingProjectError(error)) throw
|
|
651
|
-
|
|
652
|
-
"
|
|
653
|
-
"
|
|
654
|
-
|
|
655
|
-
|
|
923
|
+
async function listApps(context, provider, projectId, branchName) {
|
|
924
|
+
return provider.listApps(projectId, { branchName }).then(sortApps).catch((error) => {
|
|
925
|
+
if (isMissingProjectError(error)) throw new CliError({
|
|
926
|
+
code: "PROJECT_NOT_FOUND",
|
|
927
|
+
domain: "project",
|
|
928
|
+
summary: "Project not found",
|
|
929
|
+
why: `The resolved project "${projectId}" does not exist in the authenticated workspace or is no longer accessible.`,
|
|
930
|
+
fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect resolution for this directory.",
|
|
931
|
+
exitCode: 1,
|
|
932
|
+
nextSteps: ["prisma-cli project show", "prisma-cli app deploy --project <id-or-name>"]
|
|
933
|
+
});
|
|
934
|
+
throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
|
|
656
935
|
});
|
|
657
936
|
}
|
|
658
937
|
async function requirePreviewAppProvider(context) {
|
|
938
|
+
const { provider } = await requirePreviewAppProviderWithClient(context);
|
|
939
|
+
return provider;
|
|
940
|
+
}
|
|
941
|
+
async function requirePreviewAppProviderWithClient(context) {
|
|
659
942
|
const client = await requireComputeAuth(context.runtime.env);
|
|
660
|
-
if (!client) throw authRequiredError(["prisma auth login"]);
|
|
661
|
-
return
|
|
943
|
+
if (!client) throw authRequiredError(["prisma-cli auth login"]);
|
|
944
|
+
return {
|
|
945
|
+
client,
|
|
946
|
+
provider: createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env))
|
|
947
|
+
};
|
|
662
948
|
}
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
if (
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
949
|
+
function createPreviewLogAuthOptions(env) {
|
|
950
|
+
const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
|
|
951
|
+
if (rawToken) return {
|
|
952
|
+
baseUrl: getApiBaseUrl(env),
|
|
953
|
+
getToken: async () => rawToken
|
|
954
|
+
};
|
|
955
|
+
const tokenStorage = new FileTokenStorage(env);
|
|
956
|
+
return {
|
|
957
|
+
baseUrl: getApiBaseUrl(env),
|
|
958
|
+
getToken: async () => {
|
|
959
|
+
const tokens = await tokenStorage.getTokens();
|
|
960
|
+
if (!tokens) throw new Error("Authentication token is no longer available. Run prisma-cli auth login and try again.");
|
|
961
|
+
return tokens.accessToken;
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
async function requireProviderAndProjectContext(context, explicitProject, options) {
|
|
966
|
+
const { client, provider } = await requirePreviewAppProviderWithClient(context);
|
|
967
|
+
const target = await resolveProjectContext(context, client, provider, explicitProject, options);
|
|
968
|
+
return {
|
|
969
|
+
client,
|
|
970
|
+
provider,
|
|
971
|
+
target,
|
|
972
|
+
projectId: target.project.id
|
|
973
|
+
};
|
|
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
|
+
};
|
|
675
984
|
}
|
|
676
|
-
async function
|
|
677
|
-
const
|
|
678
|
-
if (
|
|
679
|
-
await
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
985
|
+
async function resolveProjectContext(context, client, provider, explicitProject, options) {
|
|
986
|
+
const authState = await requireAuthenticatedAuthState(context);
|
|
987
|
+
if (!authState.workspace) throw workspaceRequiredError();
|
|
988
|
+
const resolved = await resolveProjectTarget({
|
|
989
|
+
context,
|
|
990
|
+
workspace: authState.workspace,
|
|
991
|
+
explicitProject,
|
|
992
|
+
listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
|
|
993
|
+
createProject: options?.allowCreate ? async (name) => {
|
|
994
|
+
const project = await provider.createProject({ name }).catch((error) => {
|
|
995
|
+
throw createProjectOnFirstDeployError({
|
|
996
|
+
error,
|
|
997
|
+
inferredName: name,
|
|
998
|
+
workspaceName: authState.workspace.name
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
return {
|
|
1002
|
+
id: project.id,
|
|
1003
|
+
name: project.name,
|
|
1004
|
+
workspace: authState.workspace
|
|
1005
|
+
};
|
|
1006
|
+
} : void 0,
|
|
1007
|
+
allowCreate: options?.allowCreate,
|
|
1008
|
+
prompt: createSelectPromptPort(context),
|
|
1009
|
+
remember: true
|
|
683
1010
|
});
|
|
1011
|
+
const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
|
|
1012
|
+
return {
|
|
1013
|
+
...resolved,
|
|
1014
|
+
branch: {
|
|
1015
|
+
name: branch.name,
|
|
1016
|
+
kind: toBranchKind(branch.name)
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
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
|
+
}
|
|
1102
|
+
function toBranchKind(name) {
|
|
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;
|
|
684
1123
|
try {
|
|
685
|
-
await
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1124
|
+
const head = (await readFile(headPath, "utf8")).trim();
|
|
1125
|
+
if (head.startsWith("ref: refs/heads/")) return head.slice(16);
|
|
1126
|
+
} catch {
|
|
1127
|
+
return null;
|
|
689
1128
|
}
|
|
690
|
-
return
|
|
1129
|
+
return null;
|
|
691
1130
|
}
|
|
692
|
-
async function
|
|
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 {}
|
|
693
1136
|
try {
|
|
694
|
-
await
|
|
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;
|
|
695
1393
|
} catch (error) {
|
|
696
|
-
|
|
697
|
-
|
|
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}` : "~";
|
|
698
1407
|
}
|
|
1408
|
+
return resolved;
|
|
1409
|
+
}
|
|
1410
|
+
async function readCurrentWorkspaceId(context) {
|
|
1411
|
+
const state = await context.stateStore.read();
|
|
1412
|
+
if (state.auth?.workspaceId) return state.auth.workspaceId;
|
|
1413
|
+
return (await readAuthState(context.runtime.env)).workspace?.id ?? null;
|
|
699
1414
|
}
|
|
700
1415
|
function normalizeBuildType(requestedBuildType) {
|
|
701
1416
|
if (!requestedBuildType) return "auto";
|
|
702
|
-
if (requestedBuildType
|
|
703
|
-
throw usageError(`Unsupported build type "${requestedBuildType}"`,
|
|
1417
|
+
if (isPreviewBuildType(requestedBuildType)) return requestedBuildType;
|
|
1418
|
+
throw usageError(`Unsupported build type "${requestedBuildType}"`, `Only ${PREVIEW_BUILD_TYPES.join(", ")} are supported in the current preview.`, "Pass a supported --build-type value.", getBuildTypeExamples("build"), "app");
|
|
1419
|
+
}
|
|
1420
|
+
function isPreviewBuildType(value) {
|
|
1421
|
+
return PREVIEW_BUILD_TYPES.includes(value);
|
|
1422
|
+
}
|
|
1423
|
+
function getBuildTypeExamples(commandName) {
|
|
1424
|
+
return RESOLVED_PREVIEW_BUILD_TYPES.map((buildType) => {
|
|
1425
|
+
return `prisma-cli app ${commandName} --build-type ${buildType}${buildType === "bun" ? " --entry server.ts" : ""}`;
|
|
1426
|
+
});
|
|
704
1427
|
}
|
|
705
1428
|
function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
|
|
706
|
-
if (buildType
|
|
1429
|
+
if (buildType !== "auto" && buildType !== "bun" && entrypoint) throw usageError(`App ${commandName} does not accept --entry with --build-type ${buildType}`, `${formatBuildTypeName(buildType)} apps do not use an entrypoint flag in the current preview.`, `Remove --entry, or rerun prisma-cli app ${commandName} with --build-type bun when you want to target a Bun entrypoint directly.`, [`prisma-cli app ${commandName} --build-type ${buildType}`, `prisma-cli app ${commandName} --build-type bun --entry server.ts`], "app");
|
|
707
1430
|
}
|
|
708
1431
|
async function requireLocalBuildType(context, buildType, commandName) {
|
|
709
1432
|
const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
|
|
710
1433
|
if (resolvedBuildType) return resolvedBuildType;
|
|
711
|
-
throw usageError(`App ${commandName} requires an explicit framework when detection is ambiguous`, "This preview only
|
|
1434
|
+
throw usageError(`App ${commandName} requires an explicit framework when detection is ambiguous`, "This preview only starts local dev servers for clear Next.js or Bun project shapes.", "Pass --build-type nextjs for a Next.js app, or pass --build-type bun with --entry <path> for a Bun app.", [`prisma-cli app ${commandName} --build-type nextjs`, `prisma-cli app ${commandName} --build-type bun --entry server.ts`], "app");
|
|
712
1435
|
}
|
|
713
1436
|
function parseLocalPort(requestedPort) {
|
|
714
1437
|
if (!requestedPort) return DEFAULT_LOCAL_DEV_PORT;
|
|
715
1438
|
const port = Number.parseInt(requestedPort, 10);
|
|
716
|
-
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid port "${requestedPort}"`, "Port must be an integer between 1 and 65535.", "Pass --port <number> with a valid local port value.", ["prisma app run --port 3000"], "app");
|
|
1439
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid port "${requestedPort}"`, "Port must be an integer between 1 and 65535.", "Pass --port <number> with a valid local port value.", ["prisma-cli app run --port 3000"], "app");
|
|
717
1440
|
return port;
|
|
718
1441
|
}
|
|
719
1442
|
function parseDeployPortMapping(requestedPort) {
|
|
720
1443
|
if (!requestedPort) return;
|
|
1444
|
+
return { http: parseDeployHttpPort(requestedPort) };
|
|
1445
|
+
}
|
|
1446
|
+
function parseDeployHttpPort(requestedPort) {
|
|
721
1447
|
const port = Number.parseInt(requestedPort, 10);
|
|
722
|
-
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 app deploy --http-port 3000"], "app");
|
|
723
|
-
return
|
|
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");
|
|
1449
|
+
return port;
|
|
724
1450
|
}
|
|
725
1451
|
function ensurePreviewAppMode(context) {
|
|
726
1452
|
if (isRealMode(context)) return;
|
|
727
|
-
throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma auth login", "prisma project
|
|
728
|
-
}
|
|
729
|
-
function blockedPreviewAppCommandError(summary, why) {
|
|
730
|
-
return featureUnavailableError(summary, why, "Use prisma app show, prisma app open, prisma app deploy, or prisma app list-deploys in the current preview.", ["prisma app show", "prisma app list-deploys"], "app");
|
|
1453
|
+
throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project show"], "app");
|
|
731
1454
|
}
|
|
732
1455
|
function deployFailedError(summary, error, nextSteps) {
|
|
733
1456
|
return new CliError({
|
|
@@ -741,15 +1464,138 @@ function deployFailedError(summary, error, nextSteps) {
|
|
|
741
1464
|
nextSteps
|
|
742
1465
|
});
|
|
743
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
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* `app deploy` falls into "create a new project on first deploy" when no
|
|
1546
|
+
* existing project matches the package.json name (or the cwd basename as a
|
|
1547
|
+
* fallback). When the create call fails the user often doesn't realise the
|
|
1548
|
+
* CLI was attempting to create a project at all — they thought the deploy
|
|
1549
|
+
* would find an existing project. Surface that context, and recommend the
|
|
1550
|
+
* explicit `--project` flag as the unambiguous way out.
|
|
1551
|
+
*/
|
|
1552
|
+
function createProjectOnFirstDeployError(options) {
|
|
1553
|
+
const { error, inferredName, workspaceName } = options;
|
|
1554
|
+
const status = extractHttpStatus(error);
|
|
1555
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1556
|
+
const inferredContext = `No existing project matched the package.json name \`${inferredName}\`, so the CLI attempted to create one.`;
|
|
1557
|
+
const nextSteps = ["prisma-cli project list", "prisma-cli app deploy --project <id-or-name>"];
|
|
1558
|
+
if (status === 401 || status === 403) return new CliError({
|
|
1559
|
+
code: "AUTH_FORBIDDEN",
|
|
1560
|
+
domain: "auth",
|
|
1561
|
+
summary: "Could not create a new project for this deploy",
|
|
1562
|
+
why: `${inferredContext} The platform rejected the create (HTTP ${status}).`,
|
|
1563
|
+
fix: `Pass --project <id-or-name> to deploy into an existing project, or grant the service token project-create permission on workspace \`${workspaceName}\`.`,
|
|
1564
|
+
debug: formatDebugDetails(error),
|
|
1565
|
+
exitCode: 1,
|
|
1566
|
+
nextSteps
|
|
1567
|
+
});
|
|
1568
|
+
return new CliError({
|
|
1569
|
+
code: "DEPLOY_FAILED",
|
|
1570
|
+
domain: "app",
|
|
1571
|
+
summary: "Could not create a new project for this deploy",
|
|
1572
|
+
why: `${inferredContext} ${errorMessage}`.trim(),
|
|
1573
|
+
fix: "Pass --project <id-or-name> to deploy into an existing project, or retry after addressing the platform error above.",
|
|
1574
|
+
debug: formatDebugDetails(error),
|
|
1575
|
+
exitCode: 1,
|
|
1576
|
+
nextSteps
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
function extractHttpStatus(error) {
|
|
1580
|
+
if (!error || typeof error !== "object") return null;
|
|
1581
|
+
const candidate = error;
|
|
1582
|
+
if (typeof candidate.statusCode === "number") return candidate.statusCode;
|
|
1583
|
+
if (typeof candidate.status === "number") return candidate.status;
|
|
1584
|
+
if (typeof candidate.message === "string") {
|
|
1585
|
+
const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
|
|
1586
|
+
if (match) return Number.parseInt(match[1], 10);
|
|
1587
|
+
}
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
744
1590
|
function noDeploymentsError(summary, why) {
|
|
745
1591
|
return new CliError({
|
|
746
1592
|
code: "NO_DEPLOYMENTS",
|
|
747
1593
|
domain: "app",
|
|
748
1594
|
summary,
|
|
749
1595
|
why,
|
|
750
|
-
fix: "Run prisma app deploy first, or use prisma app show to inspect the current app state.",
|
|
1596
|
+
fix: "Run prisma-cli app deploy first, or use prisma-cli app show to inspect the current app state.",
|
|
751
1597
|
exitCode: 1,
|
|
752
|
-
nextSteps: ["prisma app deploy", "prisma app show"]
|
|
1598
|
+
nextSteps: ["prisma-cli app deploy", "prisma-cli app show"]
|
|
753
1599
|
});
|
|
754
1600
|
}
|
|
755
1601
|
function buildFailedError(summary, error) {
|
|
@@ -758,10 +1604,10 @@ function buildFailedError(summary, error) {
|
|
|
758
1604
|
domain: "app",
|
|
759
1605
|
summary,
|
|
760
1606
|
why: error instanceof Error ? error.message : String(error),
|
|
761
|
-
fix: "Inspect the framework output, fix the build issue, and rerun prisma app build.",
|
|
1607
|
+
fix: "Inspect the framework output, fix the build issue, and rerun prisma-cli app build.",
|
|
762
1608
|
debug: formatDebugDetails(error),
|
|
763
1609
|
exitCode: 1,
|
|
764
|
-
nextSteps: ["prisma app build", "prisma app deploy"]
|
|
1610
|
+
nextSteps: ["prisma-cli app build", "prisma-cli app deploy"]
|
|
765
1611
|
});
|
|
766
1612
|
}
|
|
767
1613
|
function runFailedError(summary, error, exitCode = 1) {
|
|
@@ -770,14 +1616,27 @@ function runFailedError(summary, error, exitCode = 1) {
|
|
|
770
1616
|
domain: "app",
|
|
771
1617
|
summary,
|
|
772
1618
|
why: error instanceof Error ? error.message : String(error),
|
|
773
|
-
fix: "Inspect the framework output above, fix the issue, and rerun prisma app run.",
|
|
1619
|
+
fix: "Inspect the framework output above, fix the issue, and rerun prisma-cli app run.",
|
|
774
1620
|
exitCode,
|
|
775
|
-
nextSteps: ["prisma app run"]
|
|
1621
|
+
nextSteps: ["prisma-cli app run"]
|
|
776
1622
|
});
|
|
777
1623
|
}
|
|
778
1624
|
function formatFrameworkName(framework) {
|
|
779
1625
|
return framework === "nextjs" ? "Next.js" : "Bun";
|
|
780
1626
|
}
|
|
1627
|
+
function isAutoBuildDetectionError(error) {
|
|
1628
|
+
return error instanceof Error && error.message.startsWith("Entrypoint is required.");
|
|
1629
|
+
}
|
|
1630
|
+
function formatBuildTypeName(buildType) {
|
|
1631
|
+
switch (buildType) {
|
|
1632
|
+
case "nextjs": return "Next.js";
|
|
1633
|
+
case "nuxt": return "Nuxt";
|
|
1634
|
+
case "astro": return "Astro";
|
|
1635
|
+
case "tanstack-start": return "TanStack Start";
|
|
1636
|
+
case "bun": return "Bun";
|
|
1637
|
+
case "auto": return "Auto";
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
781
1640
|
function removeFailedError(summary, error, nextSteps) {
|
|
782
1641
|
return new CliError({
|
|
783
1642
|
code: "REMOVE_FAILED",
|
|
@@ -803,11 +1662,30 @@ function isMissingProjectError(error) {
|
|
|
803
1662
|
function findAppByName(apps, name) {
|
|
804
1663
|
return apps.find((app) => app.name === name);
|
|
805
1664
|
}
|
|
1665
|
+
function findAppsByName(apps, name) {
|
|
1666
|
+
return apps.filter((app) => app.name === name);
|
|
1667
|
+
}
|
|
806
1668
|
function sortApps(apps) {
|
|
807
1669
|
return apps.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
|
|
808
1670
|
}
|
|
809
1671
|
function toOptionalEnvVars(envVars) {
|
|
810
1672
|
return Object.keys(envVars).length > 0 ? envVars : void 0;
|
|
811
1673
|
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Emits a deprecation banner to stderr when the legacy single-shot
|
|
1676
|
+
* env-var commands are invoked. The banner is suppressed in --json
|
|
1677
|
+
* mode so machine consumers keep their JSON channel clean; --json
|
|
1678
|
+
* users discover the deprecation via release notes and the new
|
|
1679
|
+
* `prisma-cli project env` namespace's output anyway.
|
|
1680
|
+
*
|
|
1681
|
+
* Removal of these legacy commands is deliberately scoped out of the
|
|
1682
|
+
* Public Beta — see the Compute Beta plan, sub-track 3B.1, where the
|
|
1683
|
+
* Terminal team picks an explicit removal milestone.
|
|
1684
|
+
*/
|
|
1685
|
+
function emitLegacyEnvDeprecationWarning(context, legacyCommand, replacement) {
|
|
1686
|
+
if (context.flags.json) return;
|
|
1687
|
+
const message = `[deprecation] \`prisma-cli ${legacyCommand}\` is deprecated. Use \`prisma-cli ${replacement}\` instead.`;
|
|
1688
|
+
context.runtime.stderr.write(`${message}\n`);
|
|
1689
|
+
}
|
|
812
1690
|
//#endregion
|
|
813
1691
|
export { runAppBuild, runAppDeploy, runAppListDeploys, runAppListEnv, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy, runAppUpdateEnv };
|