@prisma/cli 3.0.0-alpha.9 → 3.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -14
- package/dist/adapters/token-storage.js +57 -1
- package/dist/commands/app/index.js +119 -35
- package/dist/commands/env.js +17 -9
- package/dist/commands/project/index.js +28 -2
- package/dist/controllers/app-env.js +156 -32
- package/dist/controllers/app.js +682 -396
- package/dist/controllers/project.js +177 -21
- package/dist/lib/app/domain-guidance.js +14 -0
- package/dist/lib/app/env-config.js +16 -6
- package/dist/lib/app/preview-build.js +50 -5
- package/dist/lib/app/preview-progress.js +1 -25
- package/dist/lib/app/preview-provider.js +99 -1
- package/dist/lib/auth/auth-ops.js +49 -8
- package/dist/lib/project/interactive-setup.js +56 -0
- package/dist/lib/project/resolution.js +124 -96
- package/dist/lib/project/setup.js +88 -0
- package/dist/presenters/app-env.js +4 -3
- package/dist/presenters/app.js +172 -73
- package/dist/presenters/auth.js +19 -6
- package/dist/presenters/project.js +44 -15
- package/dist/shell/command-arguments.js +6 -0
- package/dist/shell/command-meta.js +120 -24
- package/dist/shell/command-runner.js +21 -11
- package/dist/shell/errors.js +4 -1
- package/dist/shell/output.js +3 -1
- package/dist/use-cases/auth.js +15 -4
- package/package.json +4 -4
package/dist/controllers/app.js
CHANGED
|
@@ -9,14 +9,18 @@ import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
|
9
9
|
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
10
10
|
import { parseEnvAssignments } from "../lib/app/env-vars.js";
|
|
11
11
|
import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
|
|
12
|
-
import { readBunPackageJson } from "../lib/app/bun-project.js";
|
|
12
|
+
import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
|
|
13
13
|
import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
|
|
14
|
-
import {
|
|
15
|
-
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
14
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
15
|
+
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
16
|
+
import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
17
|
+
import { bindProjectToDirectory, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
18
|
+
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
16
19
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
17
20
|
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
18
|
-
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress
|
|
19
|
-
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
21
|
+
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
|
|
22
|
+
import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
23
|
+
import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
|
|
20
24
|
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
21
25
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
22
26
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
@@ -27,8 +31,10 @@ import open from "open";
|
|
|
27
31
|
const DEPLOY_FRAMEWORKS = [
|
|
28
32
|
"nextjs",
|
|
29
33
|
"hono",
|
|
30
|
-
"tanstack-start"
|
|
34
|
+
"tanstack-start",
|
|
35
|
+
"bun"
|
|
31
36
|
];
|
|
37
|
+
const TANSTACK_START_PACKAGES = ["@tanstack/react-start", "@tanstack/solid-start"];
|
|
32
38
|
const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
|
|
33
39
|
const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
|
|
34
40
|
const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
|
|
@@ -95,67 +101,65 @@ async function runAppDeploy(context, appName, options) {
|
|
|
95
101
|
ensurePreviewAppMode(context);
|
|
96
102
|
const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
|
|
97
103
|
const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
|
|
98
|
-
|
|
104
|
+
assertExclusiveDeployProjectInputs({
|
|
105
|
+
projectRef: options?.projectRef,
|
|
106
|
+
createProjectName: options?.createProjectName,
|
|
107
|
+
envProjectId
|
|
108
|
+
});
|
|
109
|
+
const skipLocalPin = Boolean(envProjectId || options?.projectRef || options?.createProjectName);
|
|
99
110
|
const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
|
|
100
111
|
if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
|
|
101
|
-
const explicitBuildType = Boolean(options?.buildType && options.buildType !== "auto");
|
|
102
112
|
const branch = await resolveDeployBranch(context, options?.branchName);
|
|
103
113
|
if (options?.httpPort) parseDeployHttpPort(options.httpPort);
|
|
104
|
-
|
|
114
|
+
assertSupportedEntrypointForRequestedDeployShape({
|
|
105
115
|
requestedFramework: options?.framework,
|
|
106
|
-
|
|
107
|
-
explicitBuildType
|
|
116
|
+
entrypoint: options?.entrypoint
|
|
108
117
|
});
|
|
109
|
-
let runtime = resolveDeployRuntime(options?.httpPort, framework);
|
|
110
|
-
assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
|
|
111
|
-
const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
|
|
112
|
-
const firstDeploy = !skipLocalPin && localPin.kind === "missing";
|
|
113
118
|
const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
|
|
114
|
-
allowCreate: true,
|
|
115
119
|
branch,
|
|
120
|
+
createProjectName: options?.createProjectName,
|
|
116
121
|
envProjectId,
|
|
117
122
|
localPin
|
|
118
123
|
});
|
|
124
|
+
let localPinResult;
|
|
125
|
+
if (target.localPinAction) {
|
|
126
|
+
const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction);
|
|
127
|
+
localPinResult = setupResult.localPin;
|
|
128
|
+
maybeRenderProjectLinked(context, setupResult.directory, setupResult.project.name, setupResult.localPin.path);
|
|
129
|
+
}
|
|
130
|
+
let framework = await resolveDeployFramework(context, {
|
|
131
|
+
requestedFramework: options?.framework,
|
|
132
|
+
entrypoint: options?.entrypoint
|
|
133
|
+
});
|
|
134
|
+
let runtime = resolveDeployRuntime(options?.httpPort, framework);
|
|
135
|
+
assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
|
|
136
|
+
const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
|
|
119
137
|
const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
|
|
120
138
|
explicitAppName: appName,
|
|
121
139
|
explicitAppId: envAppId,
|
|
122
|
-
firstDeploy,
|
|
140
|
+
firstDeploy: Boolean(target.localPinAction),
|
|
123
141
|
inferName: () => inferTargetName(context.runtime.cwd)
|
|
124
142
|
});
|
|
125
143
|
await maybeRenderDeploySetupBlock(context, {
|
|
126
|
-
|
|
127
|
-
workspaceName: target.workspace.name,
|
|
144
|
+
includeDirectory: !target.localPinAction,
|
|
128
145
|
projectName: target.project.name,
|
|
129
|
-
projectAnnotation: annotationForProjectResolution(target.resolution),
|
|
130
146
|
branchName: target.branch.name,
|
|
131
|
-
|
|
132
|
-
appName: selectedApp.displayName,
|
|
133
|
-
appAnnotation: selectedApp.annotation,
|
|
134
|
-
framework,
|
|
135
|
-
runtime
|
|
147
|
+
appName: selectedApp.displayName
|
|
136
148
|
});
|
|
137
149
|
const customized = await maybeCustomizeDeploySettings(context, {
|
|
138
150
|
framework,
|
|
139
151
|
runtime,
|
|
140
152
|
firstDeploy: selectedApp.firstDeploy,
|
|
141
153
|
explicitFramework: Boolean(options?.framework),
|
|
142
|
-
|
|
154
|
+
explicitEntrypoint: Boolean(options?.entrypoint),
|
|
143
155
|
explicitHttpPort: Boolean(options?.httpPort)
|
|
144
156
|
});
|
|
145
157
|
framework = customized.framework;
|
|
146
158
|
runtime = customized.runtime;
|
|
147
159
|
const buildType = framework.buildType;
|
|
148
160
|
assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
|
|
161
|
+
const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint);
|
|
149
162
|
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
163
|
const progressState = createPreviewDeployProgressState();
|
|
160
164
|
const deployStartedAt = Date.now();
|
|
161
165
|
const deployResult = await provider.deployApp({
|
|
@@ -165,7 +169,7 @@ async function runAppDeploy(context, appName, options) {
|
|
|
165
169
|
appId: selectedApp.appId,
|
|
166
170
|
appName: selectedApp.appName,
|
|
167
171
|
region: selectedApp.region,
|
|
168
|
-
entrypoint
|
|
172
|
+
entrypoint,
|
|
169
173
|
buildType,
|
|
170
174
|
portMapping,
|
|
171
175
|
envVars,
|
|
@@ -193,148 +197,15 @@ async function runAppDeploy(context, appName, options) {
|
|
|
193
197
|
},
|
|
194
198
|
deployment: deployResult.deployment,
|
|
195
199
|
durationMs: deployDurationMs,
|
|
196
|
-
localPin:
|
|
197
|
-
path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
198
|
-
written: true
|
|
199
|
-
} : void 0
|
|
200
|
+
localPin: localPinResult
|
|
200
201
|
},
|
|
201
202
|
warnings: [],
|
|
202
203
|
nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
|
|
203
204
|
};
|
|
204
205
|
}
|
|
205
|
-
async function runAppUpdateEnv(context, appName, envAssignments, projectRef) {
|
|
206
|
-
ensurePreviewAppMode(context);
|
|
207
|
-
emitLegacyEnvDeprecationWarning(context, "app update-env", "project env add");
|
|
208
|
-
const envVars = parseEnvAssignments(envAssignments, {
|
|
209
|
-
commandName: "update-env",
|
|
210
|
-
requireAtLeastOne: true
|
|
211
|
-
});
|
|
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.");
|
|
215
|
-
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
216
|
-
throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
|
|
217
|
-
});
|
|
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.`);
|
|
219
|
-
const updateResult = await provider.updateAppEnv({
|
|
220
|
-
appId: deploymentsResult.app.id,
|
|
221
|
-
envVars,
|
|
222
|
-
progress: createPreviewUpdateEnvProgress(context.output.stderr, !context.flags.json && !context.flags.quiet),
|
|
223
|
-
promoteProgress: createPreviewPromoteProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
|
|
224
|
-
}).catch((error) => {
|
|
225
|
-
throw deployFailedError("Failed to update app environment variables", error, ["prisma-cli app list-env"]);
|
|
226
|
-
});
|
|
227
|
-
await context.stateStore.setSelectedApp(projectId, {
|
|
228
|
-
id: updateResult.app.id,
|
|
229
|
-
name: updateResult.app.name
|
|
230
|
-
});
|
|
231
|
-
await context.stateStore.setKnownLiveDeployment(projectId, updateResult.app.id, updateResult.deployment.id);
|
|
232
|
-
return {
|
|
233
|
-
command: "app.update-env",
|
|
234
|
-
result: {
|
|
235
|
-
projectId: updateResult.projectId,
|
|
236
|
-
app: {
|
|
237
|
-
id: updateResult.app.id,
|
|
238
|
-
name: updateResult.app.name
|
|
239
|
-
},
|
|
240
|
-
deployment: updateResult.deployment,
|
|
241
|
-
variables: updateResult.variables
|
|
242
|
-
},
|
|
243
|
-
warnings: [],
|
|
244
|
-
nextSteps: ["prisma-cli app list-env", `prisma-cli app show-deploy ${updateResult.deployment.id}`]
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
async function runAppListEnv(context, appName, projectRef) {
|
|
248
|
-
ensurePreviewAppMode(context);
|
|
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);
|
|
252
|
-
if (!selectedApp) return {
|
|
253
|
-
command: "app.list-env",
|
|
254
|
-
result: {
|
|
255
|
-
projectId,
|
|
256
|
-
app: null,
|
|
257
|
-
deployment: null,
|
|
258
|
-
variables: []
|
|
259
|
-
},
|
|
260
|
-
warnings: [],
|
|
261
|
-
nextSteps: ["prisma-cli app deploy"]
|
|
262
|
-
};
|
|
263
|
-
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
264
|
-
throw deployFailedError("Failed to inspect app deployments", error, ["prisma-cli app list-deploys"]);
|
|
265
|
-
});
|
|
266
|
-
const knownLiveDeploymentId = await context.stateStore.readKnownLiveDeployment(projectId, deploymentsResult.app.id);
|
|
267
|
-
const missingKnownLiveDeploymentId = knownLiveDeploymentId && !deploymentsResult.deployments.some((candidate) => candidate.id === knownLiveDeploymentId) ? knownLiveDeploymentId : null;
|
|
268
|
-
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
269
|
-
const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId).slice().sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id));
|
|
270
|
-
const deployment = currentLiveDeploymentId ? deployments.find((candidate) => candidate.id === currentLiveDeploymentId) ?? null : null;
|
|
271
|
-
await context.stateStore.setSelectedApp(projectId, {
|
|
272
|
-
id: deploymentsResult.app.id,
|
|
273
|
-
name: deploymentsResult.app.name
|
|
274
|
-
});
|
|
275
|
-
if (missingKnownLiveDeploymentId) {
|
|
276
|
-
const envResult = await provider.listAppEnvNames({
|
|
277
|
-
appId: deploymentsResult.app.id,
|
|
278
|
-
deploymentId: missingKnownLiveDeploymentId
|
|
279
|
-
}).catch((error) => {
|
|
280
|
-
throw deployFailedError("Failed to inspect app environment variables", error, ["prisma-cli app list-deploys"]);
|
|
281
|
-
});
|
|
282
|
-
return {
|
|
283
|
-
command: "app.list-env",
|
|
284
|
-
result: {
|
|
285
|
-
projectId,
|
|
286
|
-
app: {
|
|
287
|
-
id: envResult.app.id,
|
|
288
|
-
name: envResult.app.name
|
|
289
|
-
},
|
|
290
|
-
deployment: envResult.deployment,
|
|
291
|
-
variables: envResult.variables
|
|
292
|
-
},
|
|
293
|
-
warnings: [],
|
|
294
|
-
nextSteps: [`prisma-cli app show-deploy ${envResult.deployment.id}`]
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
if (!deployment) return {
|
|
298
|
-
command: "app.list-env",
|
|
299
|
-
result: {
|
|
300
|
-
projectId,
|
|
301
|
-
app: {
|
|
302
|
-
id: deploymentsResult.app.id,
|
|
303
|
-
name: deploymentsResult.app.name
|
|
304
|
-
},
|
|
305
|
-
deployment: null,
|
|
306
|
-
variables: []
|
|
307
|
-
},
|
|
308
|
-
warnings: [],
|
|
309
|
-
nextSteps: ["prisma-cli app deploy"]
|
|
310
|
-
};
|
|
311
|
-
const envResult = await provider.listAppEnvNames({
|
|
312
|
-
appId: deploymentsResult.app.id,
|
|
313
|
-
deploymentId: deployment.id
|
|
314
|
-
}).catch((error) => {
|
|
315
|
-
throw deployFailedError("Failed to inspect app environment variables", error, ["prisma-cli app list-deploys"]);
|
|
316
|
-
});
|
|
317
|
-
return {
|
|
318
|
-
command: "app.list-env",
|
|
319
|
-
result: {
|
|
320
|
-
projectId,
|
|
321
|
-
app: {
|
|
322
|
-
id: envResult.app.id,
|
|
323
|
-
name: envResult.app.name
|
|
324
|
-
},
|
|
325
|
-
deployment: {
|
|
326
|
-
...deployment,
|
|
327
|
-
live: deployment.live ?? envResult.deployment.live
|
|
328
|
-
},
|
|
329
|
-
variables: envResult.variables
|
|
330
|
-
},
|
|
331
|
-
warnings: [],
|
|
332
|
-
nextSteps: deployment.id ? [`prisma-cli app show-deploy ${deployment.id}`] : ["prisma-cli app deploy"]
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
206
|
async function runAppListDeploys(context, appName, projectRef) {
|
|
336
207
|
ensurePreviewAppMode(context);
|
|
337
|
-
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
208
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app list-deploys" });
|
|
338
209
|
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
339
210
|
if (!selectedApp) return {
|
|
340
211
|
command: "app.list-deploys",
|
|
@@ -371,7 +242,7 @@ async function runAppListDeploys(context, appName, projectRef) {
|
|
|
371
242
|
}
|
|
372
243
|
async function runAppShow(context, appName, projectRef) {
|
|
373
244
|
ensurePreviewAppMode(context);
|
|
374
|
-
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
245
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app show" });
|
|
375
246
|
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
376
247
|
if (!selectedApp) return {
|
|
377
248
|
command: "app.show",
|
|
@@ -447,7 +318,7 @@ async function runAppShowDeploy(context, deploymentId) {
|
|
|
447
318
|
}
|
|
448
319
|
async function runAppOpen(context, appName, projectRef) {
|
|
449
320
|
ensurePreviewAppMode(context);
|
|
450
|
-
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
321
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app open" });
|
|
451
322
|
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName);
|
|
452
323
|
if (!selectedApp) throw noDeploymentsError("No deployments available to open", "The resolved project does not have any deployed app yet.");
|
|
453
324
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
@@ -479,9 +350,135 @@ async function runAppOpen(context, appName, projectRef) {
|
|
|
479
350
|
nextSteps: ["prisma-cli app show", `prisma-cli app show-deploy ${liveDeployment.id}`]
|
|
480
351
|
};
|
|
481
352
|
}
|
|
353
|
+
async function runAppDomainAdd(context, hostname, options) {
|
|
354
|
+
const normalizedHostname = normalizeDomainHostname(hostname);
|
|
355
|
+
const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
|
|
356
|
+
const added = await target.provider.addDomain({
|
|
357
|
+
appId: target.app.id,
|
|
358
|
+
hostname: normalizedHostname
|
|
359
|
+
}).catch((error) => {
|
|
360
|
+
throw domainCommandError("add", error, normalizedHostname);
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
command: "app.domain.add",
|
|
364
|
+
result: {
|
|
365
|
+
...target.resultTarget,
|
|
366
|
+
domain: toAppDomainSummary(added.domain),
|
|
367
|
+
existing: added.existing
|
|
368
|
+
},
|
|
369
|
+
warnings: [],
|
|
370
|
+
nextSteps: [`prisma-cli app domain wait ${normalizedHostname}`, `prisma-cli app domain show ${normalizedHostname}`]
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function runAppDomainShow(context, hostname, options) {
|
|
374
|
+
const normalizedHostname = normalizeDomainHostname(hostname);
|
|
375
|
+
const target = await resolveAppDomainTarget(context, options, `app domain show ${normalizedHostname}`);
|
|
376
|
+
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show");
|
|
377
|
+
const detail = await target.provider.showDomain(domain.id).catch((error) => {
|
|
378
|
+
throw domainCommandError("show", error, normalizedHostname);
|
|
379
|
+
});
|
|
380
|
+
return {
|
|
381
|
+
command: "app.domain.show",
|
|
382
|
+
result: {
|
|
383
|
+
...target.resultTarget,
|
|
384
|
+
domain: toAppDomainSummary(detail)
|
|
385
|
+
},
|
|
386
|
+
warnings: [],
|
|
387
|
+
nextSteps: buildDomainShowNextSteps(detail)
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async function runAppDomainRemove(context, hostname, options) {
|
|
391
|
+
const normalizedHostname = normalizeDomainHostname(hostname);
|
|
392
|
+
const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
|
|
393
|
+
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
|
|
394
|
+
await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
|
|
395
|
+
await target.provider.removeDomain(domain.id).catch((error) => {
|
|
396
|
+
throw domainCommandError("remove", error, normalizedHostname);
|
|
397
|
+
});
|
|
398
|
+
return {
|
|
399
|
+
command: "app.domain.remove",
|
|
400
|
+
result: {
|
|
401
|
+
...target.resultTarget,
|
|
402
|
+
hostname: normalizedHostname,
|
|
403
|
+
removed: true
|
|
404
|
+
},
|
|
405
|
+
warnings: [],
|
|
406
|
+
nextSteps: []
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function runAppDomainRetry(context, hostname, options) {
|
|
410
|
+
const normalizedHostname = normalizeDomainHostname(hostname);
|
|
411
|
+
const target = await resolveAppDomainTarget(context, options, `app domain retry ${normalizedHostname}`);
|
|
412
|
+
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry");
|
|
413
|
+
const retried = await target.provider.retryDomain(domain.id).catch((error) => {
|
|
414
|
+
throw domainCommandError("retry", error, normalizedHostname);
|
|
415
|
+
});
|
|
416
|
+
return {
|
|
417
|
+
command: "app.domain.retry",
|
|
418
|
+
result: {
|
|
419
|
+
...target.resultTarget,
|
|
420
|
+
domain: toAppDomainSummary(retried)
|
|
421
|
+
},
|
|
422
|
+
warnings: [],
|
|
423
|
+
nextSteps: [`prisma-cli app domain wait ${normalizedHostname}`]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
async function runAppDomainWait(context, hostname, options) {
|
|
427
|
+
const normalizedHostname = normalizeDomainHostname(hostname);
|
|
428
|
+
const timeoutMs = parseDomainWaitTimeout(options?.timeout);
|
|
429
|
+
const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
|
|
430
|
+
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
|
|
431
|
+
if (!context.flags.json && !context.flags.quiet) context.output.stderr.write([
|
|
432
|
+
`app domain wait -> Waiting for ${normalizedHostname} to become active.`,
|
|
433
|
+
"",
|
|
434
|
+
`Workspace: ${target.resultTarget.workspace.name} Project: ${target.resultTarget.project.name} Branch: ${target.resultTarget.branch.name} App: ${target.resultTarget.app.name}`,
|
|
435
|
+
""
|
|
436
|
+
].join("\n"));
|
|
437
|
+
const start = Date.now();
|
|
438
|
+
const deadline = start + timeoutMs;
|
|
439
|
+
const pollIntervalMs = readDomainWaitPollIntervalMs(context);
|
|
440
|
+
let lastStatus = null;
|
|
441
|
+
let current = domain;
|
|
442
|
+
while (true) {
|
|
443
|
+
emitDomainWaitStatus(context, {
|
|
444
|
+
hostname: normalizedHostname,
|
|
445
|
+
domainId: current.id,
|
|
446
|
+
previousStatus: lastStatus,
|
|
447
|
+
status: current.status,
|
|
448
|
+
elapsedMs: Date.now() - start
|
|
449
|
+
});
|
|
450
|
+
lastStatus = current.status;
|
|
451
|
+
if (current.status === "active") {
|
|
452
|
+
if (!context.flags.json && !context.flags.quiet) context.output.stderr.write(`\n${normalizedHostname} is live at https://${normalizedHostname}\n`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (current.status === "failed") throw new CliError({
|
|
456
|
+
code: "DOMAIN_VERIFICATION_FAILED",
|
|
457
|
+
domain: "app",
|
|
458
|
+
summary: `Custom domain "${normalizedHostname}" failed verification`,
|
|
459
|
+
why: formatDomainFailureWhy(current),
|
|
460
|
+
fix: formatDomainFailureFix(current) ?? `Run prisma-cli app domain retry ${normalizedHostname}.`,
|
|
461
|
+
exitCode: 1,
|
|
462
|
+
nextSteps: [`prisma-cli app domain show ${normalizedHostname}`, `prisma-cli app domain retry ${normalizedHostname}`]
|
|
463
|
+
});
|
|
464
|
+
if (timeoutMs === 0 || Date.now() >= deadline) throw new CliError({
|
|
465
|
+
code: "DOMAIN_VERIFICATION_TIMEOUT",
|
|
466
|
+
domain: "app",
|
|
467
|
+
summary: `Timed out waiting for "${normalizedHostname}" to become active`,
|
|
468
|
+
why: `The domain is still "${current.status}".`,
|
|
469
|
+
fix: `Run prisma-cli app domain show ${normalizedHostname} to inspect the current status, or retry wait with a longer --timeout.`,
|
|
470
|
+
exitCode: 1,
|
|
471
|
+
nextSteps: [`prisma-cli app domain show ${normalizedHostname}`]
|
|
472
|
+
});
|
|
473
|
+
await sleep(Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)));
|
|
474
|
+
current = await target.provider.showDomain(current.id).catch((error) => {
|
|
475
|
+
throw domainCommandError("wait", error, normalizedHostname);
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
482
479
|
async function runAppLogs(context, appName, deploymentId, projectRef) {
|
|
483
480
|
ensurePreviewAppMode(context);
|
|
484
|
-
const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
481
|
+
const { provider, target: resolvedTarget, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app logs" });
|
|
485
482
|
const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, resolvedTarget.branch.name, appName);
|
|
486
483
|
if (!context.flags.json && !context.flags.quiet) {
|
|
487
484
|
const lines = renderCommandHeader(context.ui, {
|
|
@@ -605,7 +602,7 @@ function writeLogRecord(context, record) {
|
|
|
605
602
|
}
|
|
606
603
|
async function runAppPromote(context, deploymentId, appName, projectRef) {
|
|
607
604
|
ensurePreviewAppMode(context);
|
|
608
|
-
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
605
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app promote" });
|
|
609
606
|
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "promote");
|
|
610
607
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
611
608
|
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
@@ -645,7 +642,7 @@ async function runAppPromote(context, deploymentId, appName, projectRef) {
|
|
|
645
642
|
}
|
|
646
643
|
async function runAppRollback(context, appName, deploymentId, projectRef) {
|
|
647
644
|
ensurePreviewAppMode(context);
|
|
648
|
-
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
645
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app rollback" });
|
|
649
646
|
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "rollback");
|
|
650
647
|
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
651
648
|
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
@@ -687,7 +684,7 @@ async function runAppRollback(context, appName, deploymentId, projectRef) {
|
|
|
687
684
|
}
|
|
688
685
|
async function runAppRemove(context, appName, projectRef) {
|
|
689
686
|
ensurePreviewAppMode(context);
|
|
690
|
-
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef);
|
|
687
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, projectRef, { commandName: "app remove" });
|
|
691
688
|
const selectedApp = await requireReleaseAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), appName, "remove");
|
|
692
689
|
await confirmAppRemoval(context, selectedApp);
|
|
693
690
|
const removedApp = await provider.removeApp(selectedApp.id).catch((error) => {
|
|
@@ -708,6 +705,299 @@ async function runAppRemove(context, appName, projectRef) {
|
|
|
708
705
|
nextSteps: ["prisma-cli app deploy", "prisma-cli app list-deploys"]
|
|
709
706
|
};
|
|
710
707
|
}
|
|
708
|
+
async function resolveAppDomainTarget(context, options, commandName = "app domain") {
|
|
709
|
+
ensurePreviewAppMode(context);
|
|
710
|
+
const branch = resolveDomainBranch(options?.branchName);
|
|
711
|
+
if (toBranchKind(branch.name) !== "production") throw new CliError({
|
|
712
|
+
code: "BRANCH_NOT_DEPLOYABLE",
|
|
713
|
+
domain: "branch",
|
|
714
|
+
summary: "Custom domains require the production branch",
|
|
715
|
+
why: `Custom domains on preview branch "${branch.name}" are not supported in Public Beta.`,
|
|
716
|
+
fix: "Use --branch production, or attach the domain after promoting/deploying to the production branch.",
|
|
717
|
+
exitCode: 2,
|
|
718
|
+
nextSteps: ["prisma-cli app domain add <hostname> --branch production"]
|
|
719
|
+
});
|
|
720
|
+
const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
|
|
721
|
+
const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
|
|
722
|
+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
|
|
723
|
+
branch,
|
|
724
|
+
commandName,
|
|
725
|
+
envProjectId
|
|
726
|
+
});
|
|
727
|
+
const selectedApp = await resolveDomainAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
|
|
728
|
+
explicitAppName: options?.appName,
|
|
729
|
+
explicitAppId: envAppId
|
|
730
|
+
});
|
|
731
|
+
await context.stateStore.setSelectedApp(projectId, {
|
|
732
|
+
id: selectedApp.id,
|
|
733
|
+
name: selectedApp.name
|
|
734
|
+
});
|
|
735
|
+
return {
|
|
736
|
+
provider,
|
|
737
|
+
app: selectedApp,
|
|
738
|
+
resultTarget: {
|
|
739
|
+
workspace: target.workspace,
|
|
740
|
+
project: target.project,
|
|
741
|
+
branch: target.branch,
|
|
742
|
+
app: {
|
|
743
|
+
id: selectedApp.id,
|
|
744
|
+
name: selectedApp.name
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function resolveDomainBranch(explicitBranchName) {
|
|
750
|
+
return {
|
|
751
|
+
name: explicitBranchName?.trim() || "production",
|
|
752
|
+
annotation: explicitBranchName ? "set by --branch" : "production default"
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
async function resolveDomainAppSelection(context, projectId, apps, options) {
|
|
756
|
+
if (options.explicitAppId) {
|
|
757
|
+
const matched = apps.find((app) => app.id === options.explicitAppId);
|
|
758
|
+
if (!matched) throw usageError("Selected app does not exist in the resolved production branch", `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 deploy the app on the production branch.`, ["prisma-cli app deploy --branch production"], "app");
|
|
759
|
+
return matched;
|
|
760
|
+
}
|
|
761
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, apps, options.explicitAppName);
|
|
762
|
+
if (selectedApp) return selectedApp;
|
|
763
|
+
throw usageError("Custom domain requires an existing app on the production branch", "The resolved production branch does not have an app that can receive a custom domain.", "Deploy or promote an app to production first, then rerun the domain command.", ["prisma-cli app deploy --branch production", "prisma-cli app show"], "app");
|
|
764
|
+
}
|
|
765
|
+
async function resolveDomainByHostname(provider, appId, hostname, command) {
|
|
766
|
+
const matched = (await provider.listDomains(appId).catch((error) => {
|
|
767
|
+
throw domainCommandError(command, error, hostname);
|
|
768
|
+
})).find((domain) => sameDomainHostname(domain.hostname, hostname));
|
|
769
|
+
if (matched) return matched;
|
|
770
|
+
throw domainNotFoundError(hostname);
|
|
771
|
+
}
|
|
772
|
+
function normalizeDomainHostname(hostname) {
|
|
773
|
+
const normalized = hostname.trim().replace(/\.$/, "").toLowerCase();
|
|
774
|
+
if (!isValidDomainHostname(normalized)) throw new CliError({
|
|
775
|
+
code: "DOMAIN_HOSTNAME_INVALID",
|
|
776
|
+
domain: "app",
|
|
777
|
+
summary: `Invalid custom domain "${hostname}"`,
|
|
778
|
+
why: "Custom domains must be valid hostnames without protocol, path, wildcard, or port.",
|
|
779
|
+
fix: "Pass a hostname like shop.acme.com.",
|
|
780
|
+
exitCode: 2,
|
|
781
|
+
nextSteps: ["prisma-cli app domain add shop.acme.com"]
|
|
782
|
+
});
|
|
783
|
+
return normalized;
|
|
784
|
+
}
|
|
785
|
+
function isValidDomainHostname(hostname) {
|
|
786
|
+
if (hostname.length < 1 || hostname.length > 253) return false;
|
|
787
|
+
if (hostname.includes("://") || hostname.includes("/") || hostname.includes(":") || hostname.startsWith("*.")) return false;
|
|
788
|
+
const labels = hostname.split(".");
|
|
789
|
+
if (labels.length < 2) return false;
|
|
790
|
+
return labels.every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label));
|
|
791
|
+
}
|
|
792
|
+
function sameDomainHostname(left, right) {
|
|
793
|
+
return left.trim().replace(/\.$/, "").toLowerCase() === right.trim().replace(/\.$/, "").toLowerCase();
|
|
794
|
+
}
|
|
795
|
+
function toAppDomainSummary(domain) {
|
|
796
|
+
return {
|
|
797
|
+
id: domain.id,
|
|
798
|
+
type: domain.type,
|
|
799
|
+
url: domain.url,
|
|
800
|
+
hostname: domain.hostname,
|
|
801
|
+
computeServiceId: domain.computeServiceId,
|
|
802
|
+
status: domain.status,
|
|
803
|
+
foundryStatus: domain.foundryStatus,
|
|
804
|
+
failureReason: domain.failureReason,
|
|
805
|
+
failureCategory: domain.failureCategory,
|
|
806
|
+
certExpiresAt: domain.certExpiresAt,
|
|
807
|
+
createdAt: domain.createdAt,
|
|
808
|
+
updatedAt: domain.updatedAt,
|
|
809
|
+
dnsRecords: toAppDomainDnsRecords(domain)
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function toAppDomainDnsRecords(domain) {
|
|
813
|
+
return domain.dnsRecords.map((record) => ({
|
|
814
|
+
type: record.type,
|
|
815
|
+
name: record.name,
|
|
816
|
+
value: record.value,
|
|
817
|
+
ttl: record.ttl
|
|
818
|
+
}));
|
|
819
|
+
}
|
|
820
|
+
function buildDomainShowNextSteps(domain) {
|
|
821
|
+
if (domain.status === "active") return [];
|
|
822
|
+
if (domain.status === "failed") return [`prisma-cli app domain retry ${domain.hostname}`];
|
|
823
|
+
return [`prisma-cli app domain wait ${domain.hostname}`];
|
|
824
|
+
}
|
|
825
|
+
async function confirmDomainRemoval(context, target, hostname) {
|
|
826
|
+
if (context.flags.yes) return;
|
|
827
|
+
if (!canPrompt(context)) throw new CliError({
|
|
828
|
+
code: "CONFIRMATION_REQUIRED",
|
|
829
|
+
domain: "app",
|
|
830
|
+
summary: "Custom domain removal requires confirmation in the current mode",
|
|
831
|
+
why: "This command detaches a domain and cannot prompt for confirmation in the current mode.",
|
|
832
|
+
fix: `Pass --yes to confirm removal of "${hostname}", or rerun prisma-cli app domain remove in an interactive TTY.`,
|
|
833
|
+
exitCode: 1,
|
|
834
|
+
nextSteps: [`prisma-cli app domain remove ${hostname} --app ${target.app.name} --yes`]
|
|
835
|
+
});
|
|
836
|
+
if (!await confirmPrompt({
|
|
837
|
+
input: context.runtime.stdin,
|
|
838
|
+
output: context.output.stderr,
|
|
839
|
+
message: `Detach ${hostname} from App "${target.app.name}"?`,
|
|
840
|
+
initialValue: false
|
|
841
|
+
})) throw usageError("Custom domain removal canceled", "The command was canceled before the domain was detached.", "Rerun the command and confirm removal, or pass --yes.", [`prisma-cli app domain remove ${hostname} --app ${target.app.name} --yes`], "app");
|
|
842
|
+
}
|
|
843
|
+
function domainCommandError(command, error, hostname) {
|
|
844
|
+
if (error instanceof PreviewDomainApiError) {
|
|
845
|
+
if (command === "add" && (error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
|
|
846
|
+
if (command === "add" && error.status === 400) return new CliError({
|
|
847
|
+
code: "DOMAIN_HOSTNAME_INVALID",
|
|
848
|
+
domain: "app",
|
|
849
|
+
summary: `Invalid custom domain "${hostname}"`,
|
|
850
|
+
why: error.message,
|
|
851
|
+
fix: "Pass a valid hostname like shop.acme.com and make sure DNS can be verified.",
|
|
852
|
+
debug: formatDebugDetails(error),
|
|
853
|
+
exitCode: 2,
|
|
854
|
+
nextSteps: ["prisma-cli app domain add shop.acme.com"]
|
|
855
|
+
});
|
|
856
|
+
if (command === "add" && (error.status === 429 || isDomainQuotaError(error))) return new CliError({
|
|
857
|
+
code: "DOMAIN_QUOTA_EXCEEDED",
|
|
858
|
+
domain: "app",
|
|
859
|
+
summary: "Custom domain quota exceeded",
|
|
860
|
+
why: error.message,
|
|
861
|
+
fix: "Remove an existing custom domain before adding another one.",
|
|
862
|
+
debug: formatDebugDetails(error),
|
|
863
|
+
exitCode: 1,
|
|
864
|
+
nextSteps: ["prisma-cli app domain remove <hostname>"]
|
|
865
|
+
});
|
|
866
|
+
if (command === "add" && error.status === 409) return domainAlreadyRegisteredError(hostname, error);
|
|
867
|
+
if (command === "add" && error.status === 422) return new CliError({
|
|
868
|
+
code: "NO_DEPLOYMENTS",
|
|
869
|
+
domain: "app",
|
|
870
|
+
summary: "Custom domain requires a live production deployment",
|
|
871
|
+
why: "The selected production app does not have a promoted version that can receive a custom domain.",
|
|
872
|
+
fix: "Deploy the app to the production branch, then rerun the domain command.",
|
|
873
|
+
debug: formatDebugDetails(error),
|
|
874
|
+
exitCode: 1,
|
|
875
|
+
nextSteps: ["prisma-cli app deploy --branch production", `prisma-cli app domain add ${hostname}`]
|
|
876
|
+
});
|
|
877
|
+
if ((command === "show" || command === "remove" || command === "retry" || command === "wait") && error.status === 404) return domainNotFoundError(hostname);
|
|
878
|
+
if (command === "retry" && error.status === 409) return new CliError({
|
|
879
|
+
code: "DOMAIN_RETRY_NOT_ELIGIBLE",
|
|
880
|
+
domain: "app",
|
|
881
|
+
summary: `Custom domain "${hostname}" is not eligible for retry`,
|
|
882
|
+
why: error.message,
|
|
883
|
+
fix: "Wait for the current verification or TLS step to finish, then rerun retry if the domain fails.",
|
|
884
|
+
debug: formatDebugDetails(error),
|
|
885
|
+
exitCode: 1,
|
|
886
|
+
nextSteps: [`prisma-cli app domain show ${hostname}`]
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
return new CliError({
|
|
890
|
+
code: "DEPLOY_FAILED",
|
|
891
|
+
domain: "app",
|
|
892
|
+
summary: `Custom domain ${command} failed`,
|
|
893
|
+
why: error instanceof Error ? error.message : String(error),
|
|
894
|
+
fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
|
|
895
|
+
debug: formatDebugDetails(error),
|
|
896
|
+
exitCode: 1,
|
|
897
|
+
nextSteps: [`prisma-cli app domain show ${hostname}`]
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function isDomainQuotaError(error) {
|
|
901
|
+
if (error.status !== 409) return false;
|
|
902
|
+
const text = `${error.message} ${error.hint ?? ""}`.toLowerCase();
|
|
903
|
+
return text.includes("quota") || text.includes("maximum") || text.includes("limit");
|
|
904
|
+
}
|
|
905
|
+
function domainAlreadyRegisteredError(hostname, error) {
|
|
906
|
+
return new CliError({
|
|
907
|
+
code: "DOMAIN_ALREADY_REGISTERED",
|
|
908
|
+
domain: "app",
|
|
909
|
+
summary: `Custom domain "${hostname}" is already registered`,
|
|
910
|
+
why: error.hint ?? error.message,
|
|
911
|
+
fix: "Select the app that owns this hostname and remove it there, or contact support if you cannot access it.",
|
|
912
|
+
debug: formatDebugDetails(error),
|
|
913
|
+
exitCode: 1,
|
|
914
|
+
nextSteps: [`Select the owning app and remove ${hostname} there.`, "Contact Prisma support if you cannot access the owning app."]
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
function isDomainDnsError(error) {
|
|
918
|
+
const text = `${error.message} ${error.hint ?? ""}`.toLowerCase();
|
|
919
|
+
return text.includes("dns is not configured") || text.includes("dns verification failed") || text.includes("no cname") || text.includes("cname record") || text.includes("no a/aaaa") || /\bcname(?:s)?\s+to\b/.test(text);
|
|
920
|
+
}
|
|
921
|
+
function domainDnsNotConfiguredError(hostname, error) {
|
|
922
|
+
const target = extractDomainDnsTarget(error);
|
|
923
|
+
const record = target ? `CNAME ${hostname} -> ${target}` : null;
|
|
924
|
+
return new CliError({
|
|
925
|
+
code: "DOMAIN_DNS_NOT_CONFIGURED",
|
|
926
|
+
domain: "app",
|
|
927
|
+
summary: `DNS is not configured for "${hostname}"`,
|
|
928
|
+
why: error.hint ?? error.message,
|
|
929
|
+
fix: record ? `Add ${record} at your DNS provider, then rerun the domain command.` : "The platform did not return the required DNS target. Re-run with --trace for the underlying API response details.",
|
|
930
|
+
debug: formatDebugDetails(error),
|
|
931
|
+
exitCode: 1,
|
|
932
|
+
nextSteps: record ? [`add ${record}`, `prisma-cli app domain add ${hostname}`] : [`prisma-cli app domain add ${hostname} --trace`]
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function extractDomainDnsTarget(error) {
|
|
936
|
+
const text = `${error.hint ?? ""} ${error.message}`;
|
|
937
|
+
return /\b((?:[a-z0-9-]+\.)+prisma\.build)\b/i.exec(text)?.[1]?.toLowerCase() ?? null;
|
|
938
|
+
}
|
|
939
|
+
function domainNotFoundError(hostname) {
|
|
940
|
+
return new CliError({
|
|
941
|
+
code: "DOMAIN_NOT_FOUND",
|
|
942
|
+
domain: "app",
|
|
943
|
+
summary: `Custom domain "${hostname}" not found`,
|
|
944
|
+
why: "The hostname is not attached to the selected app.",
|
|
945
|
+
fix: "Check the hostname and selected app, or add the domain first.",
|
|
946
|
+
exitCode: 1,
|
|
947
|
+
nextSteps: [`prisma-cli app domain add ${hostname}`]
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
function formatDomainFailureWhy(domain) {
|
|
951
|
+
if (domain.failureReason) return domain.failureCategory ? `${domain.failureCategory}: ${domain.failureReason}` : domain.failureReason;
|
|
952
|
+
return "The platform reported a terminal failed state for this custom domain.";
|
|
953
|
+
}
|
|
954
|
+
function parseDomainWaitTimeout(value) {
|
|
955
|
+
if (!value) return 900 * 1e3;
|
|
956
|
+
const trimmed = value.trim().toLowerCase();
|
|
957
|
+
if (trimmed === "0") return 0;
|
|
958
|
+
const match = /^(\d+)(ms|s|m|h)$/.exec(trimmed);
|
|
959
|
+
if (!match) throw usageError(`Invalid timeout "${value}"`, "Timeout must be a duration such as 0, 30s, 15m, or 1h.", "Pass --timeout 15m, or --timeout 0 to poll once.", ["prisma-cli app domain wait shop.acme.com --timeout 15m"], "app");
|
|
960
|
+
const amount = Number.parseInt(match[1], 10);
|
|
961
|
+
const unit = match[2];
|
|
962
|
+
return amount * (unit === "h" ? 3600 * 1e3 : unit === "m" ? 60 * 1e3 : unit === "s" ? 1e3 : 1);
|
|
963
|
+
}
|
|
964
|
+
function readDomainWaitPollIntervalMs(context) {
|
|
965
|
+
const raw = context.runtime.env.PRISMA_CLI_DOMAIN_WAIT_POLL_MS;
|
|
966
|
+
if (!raw) return 5e3;
|
|
967
|
+
const parsed = Number.parseInt(raw, 10);
|
|
968
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : 5e3;
|
|
969
|
+
}
|
|
970
|
+
function emitDomainWaitStatus(context, event) {
|
|
971
|
+
if (context.flags.json) {
|
|
972
|
+
writeJsonEvent(context.output, {
|
|
973
|
+
type: "status",
|
|
974
|
+
command: "app.domain.wait",
|
|
975
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
976
|
+
data: {
|
|
977
|
+
hostname: event.hostname,
|
|
978
|
+
domainId: event.domainId,
|
|
979
|
+
previousStatus: event.previousStatus,
|
|
980
|
+
status: event.status,
|
|
981
|
+
elapsedMs: event.elapsedMs
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
if (context.flags.quiet) return;
|
|
987
|
+
if (event.previousStatus === event.status) return;
|
|
988
|
+
const transition = event.previousStatus ? `${event.previousStatus} -> ${event.status}` : event.status;
|
|
989
|
+
context.output.stderr.write(` ${transition} (${formatElapsed(event.elapsedMs)})\n`);
|
|
990
|
+
}
|
|
991
|
+
function formatElapsed(milliseconds) {
|
|
992
|
+
const seconds = Math.max(Math.floor(milliseconds / 1e3), 0);
|
|
993
|
+
const minutes = Math.floor(seconds / 60);
|
|
994
|
+
const remainingSeconds = seconds % 60;
|
|
995
|
+
return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
|
|
996
|
+
}
|
|
997
|
+
async function sleep(milliseconds) {
|
|
998
|
+
if (milliseconds <= 0) return;
|
|
999
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1000
|
+
}
|
|
711
1001
|
async function resolveDeployAppSelection(context, projectId, apps, options) {
|
|
712
1002
|
if (options.explicitAppName) {
|
|
713
1003
|
const matches = findAppsByName(apps, options.explicitAppName);
|
|
@@ -927,9 +1217,9 @@ async function listApps(context, provider, projectId, branchName) {
|
|
|
927
1217
|
domain: "project",
|
|
928
1218
|
summary: "Project not found",
|
|
929
1219
|
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
|
|
1220
|
+
fix: "Pass --project <id-or-name>, or run prisma-cli project show to inspect this directory's binding.",
|
|
931
1221
|
exitCode: 1,
|
|
932
|
-
nextSteps: ["prisma-cli project show", "prisma-cli
|
|
1222
|
+
nextSteps: ["prisma-cli project show", "prisma-cli project link <id-or-name>"]
|
|
933
1223
|
});
|
|
934
1224
|
throw deployFailedError("Failed to list apps", error, ["prisma-cli project show"]);
|
|
935
1225
|
});
|
|
@@ -964,7 +1254,7 @@ function createPreviewLogAuthOptions(env) {
|
|
|
964
1254
|
}
|
|
965
1255
|
async function requireProviderAndProjectContext(context, explicitProject, options) {
|
|
966
1256
|
const { client, provider } = await requirePreviewAppProviderWithClient(context);
|
|
967
|
-
const target = await resolveProjectContext(context, client,
|
|
1257
|
+
const target = await resolveProjectContext(context, client, explicitProject, options);
|
|
968
1258
|
return {
|
|
969
1259
|
client,
|
|
970
1260
|
provider,
|
|
@@ -982,31 +1272,16 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
|
|
|
982
1272
|
projectId: target.project.id
|
|
983
1273
|
};
|
|
984
1274
|
}
|
|
985
|
-
async function resolveProjectContext(context, client,
|
|
1275
|
+
async function resolveProjectContext(context, client, explicitProject, options) {
|
|
986
1276
|
const authState = await requireAuthenticatedAuthState(context);
|
|
987
1277
|
if (!authState.workspace) throw workspaceRequiredError();
|
|
988
1278
|
const resolved = await resolveProjectTarget({
|
|
989
1279
|
context,
|
|
990
1280
|
workspace: authState.workspace,
|
|
991
1281
|
explicitProject,
|
|
1282
|
+
envProjectId: options?.envProjectId,
|
|
992
1283
|
listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
|
|
993
|
-
|
|
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
|
|
1284
|
+
commandName: options?.commandName
|
|
1010
1285
|
});
|
|
1011
1286
|
const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
|
|
1012
1287
|
return {
|
|
@@ -1022,30 +1297,30 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1022
1297
|
if (!workspace) throw workspaceRequiredError();
|
|
1023
1298
|
const branch = options.branch ?? await resolveDeployBranch(context, void 0);
|
|
1024
1299
|
const projects = await listRealWorkspaceProjects(client, workspace);
|
|
1025
|
-
|
|
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,
|
|
1300
|
+
if (explicitProject) return withDeployBranch({
|
|
1041
1301
|
workspace,
|
|
1042
|
-
explicitProject,
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1302
|
+
project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
|
|
1303
|
+
resolution: {
|
|
1304
|
+
projectSource: "explicit",
|
|
1305
|
+
targetName: explicitProject,
|
|
1306
|
+
targetNameSource: "explicit"
|
|
1307
|
+
},
|
|
1308
|
+
localPinAction: "linked"
|
|
1309
|
+
}, branch);
|
|
1310
|
+
if (options.createProjectName) {
|
|
1311
|
+
const projectName = options.createProjectName.trim();
|
|
1312
|
+
if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
|
|
1313
|
+
return withDeployBranch({
|
|
1314
|
+
workspace,
|
|
1315
|
+
project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace)),
|
|
1316
|
+
resolution: {
|
|
1317
|
+
projectSource: "created",
|
|
1318
|
+
targetName: projectName,
|
|
1319
|
+
targetNameSource: "explicit"
|
|
1320
|
+
},
|
|
1321
|
+
localPinAction: "created"
|
|
1322
|
+
}, branch);
|
|
1323
|
+
}
|
|
1049
1324
|
if (options.envProjectId) {
|
|
1050
1325
|
const project = projects.find((candidate) => candidate.id === options.envProjectId);
|
|
1051
1326
|
if (!project) throw projectNotFoundError(options.envProjectId, workspace);
|
|
@@ -1074,15 +1349,58 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1074
1349
|
}
|
|
1075
1350
|
}, branch);
|
|
1076
1351
|
}
|
|
1077
|
-
|
|
1352
|
+
const platformMapping = await resolveDurablePlatformMapping();
|
|
1353
|
+
if (platformMapping && platformMapping.workspace.id === workspace.id) return withDeployBranch({
|
|
1354
|
+
workspace,
|
|
1355
|
+
project: toProjectSummary(platformMapping),
|
|
1356
|
+
resolution: {
|
|
1357
|
+
projectSource: "platform-mapping",
|
|
1358
|
+
targetName: platformMapping.name,
|
|
1359
|
+
targetNameSource: "platform-mapping"
|
|
1360
|
+
}
|
|
1361
|
+
}, branch);
|
|
1362
|
+
if (canPrompt(context) && !context.flags.yes) return withDeployBranch(await resolveInteractiveDeployProjectSetup(context, provider, workspace, projects), branch);
|
|
1363
|
+
throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd));
|
|
1364
|
+
}
|
|
1365
|
+
async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
|
|
1366
|
+
const setup = await promptForProjectSetupChoice({
|
|
1078
1367
|
context,
|
|
1368
|
+
projects,
|
|
1369
|
+
createProject: (projectName) => createProjectForDeploySetup(provider, projectName, workspace),
|
|
1370
|
+
cancel: {
|
|
1371
|
+
why: "Deploy needs a Project before it can continue.",
|
|
1372
|
+
fix: "Choose an existing Project or create a new one, then rerun deploy.",
|
|
1373
|
+
nextSteps: ["prisma-cli app deploy --project <id-or-name>", "prisma-cli app deploy --create-project <name>"]
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
return {
|
|
1079
1377
|
workspace,
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1378
|
+
project: setup.project,
|
|
1379
|
+
resolution: {
|
|
1380
|
+
projectSource: setup.action === "created" ? "created" : "prompt",
|
|
1381
|
+
targetName: setup.targetName,
|
|
1382
|
+
targetNameSource: setup.targetNameSource
|
|
1383
|
+
},
|
|
1384
|
+
localPinAction: setup.action
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
async function createProjectForDeploySetup(provider, projectName, workspace) {
|
|
1388
|
+
const created = await provider.createProject({ name: projectName }).catch((error) => {
|
|
1389
|
+
throw projectCreateFailedError(error, projectName, workspace, {
|
|
1390
|
+
nextSteps: [
|
|
1391
|
+
"prisma-cli project list",
|
|
1392
|
+
"prisma-cli app deploy --project <id-or-name>",
|
|
1393
|
+
`prisma-cli app deploy --create-project ${formatCommandArgument(projectName)}`
|
|
1394
|
+
],
|
|
1395
|
+
permissionFix: "Choose an existing Project with --project, or grant the token permission to create Projects in this workspace.",
|
|
1396
|
+
fallbackFix: "Choose an existing Project with --project, or retry after addressing the platform error above."
|
|
1397
|
+
});
|
|
1398
|
+
});
|
|
1399
|
+
return {
|
|
1400
|
+
id: created.id,
|
|
1401
|
+
name: created.name,
|
|
1402
|
+
workspace
|
|
1403
|
+
};
|
|
1086
1404
|
}
|
|
1087
1405
|
function withDeployBranch(target, branch) {
|
|
1088
1406
|
return {
|
|
@@ -1093,15 +1411,22 @@ function withDeployBranch(target, branch) {
|
|
|
1093
1411
|
}
|
|
1094
1412
|
};
|
|
1095
1413
|
}
|
|
1096
|
-
function toProjectSummary(project) {
|
|
1097
|
-
return {
|
|
1098
|
-
id: project.id,
|
|
1099
|
-
name: project.name
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
1414
|
function toBranchKind(name) {
|
|
1103
1415
|
return name === "production" || name === "main" ? "production" : "preview";
|
|
1104
1416
|
}
|
|
1417
|
+
function assertExclusiveDeployProjectInputs(options) {
|
|
1418
|
+
const provided = [
|
|
1419
|
+
options.projectRef ? "--project" : null,
|
|
1420
|
+
options.createProjectName ? "--create-project" : null,
|
|
1421
|
+
options.envProjectId ? PRISMA_PROJECT_ID_ENV_VAR : null
|
|
1422
|
+
].filter((value) => Boolean(value));
|
|
1423
|
+
if (provided.length <= 1) return;
|
|
1424
|
+
throw usageError("Project selection is ambiguous", `${provided.join(", ")} cannot be used together.`, "Choose exactly one Project source for this deploy.", [
|
|
1425
|
+
"prisma-cli app deploy --project <id-or-name>",
|
|
1426
|
+
"prisma-cli app deploy --create-project <name>",
|
|
1427
|
+
`unset ${PRISMA_PROJECT_ID_ENV_VAR}`
|
|
1428
|
+
], "project");
|
|
1429
|
+
}
|
|
1105
1430
|
async function resolveDeployBranch(context, explicitBranchName) {
|
|
1106
1431
|
if (explicitBranchName) return {
|
|
1107
1432
|
name: explicitBranchName,
|
|
@@ -1142,15 +1467,12 @@ async function resolveGitHeadPath(gitPath) {
|
|
|
1142
1467
|
}
|
|
1143
1468
|
async function resolveDeployFramework(context, options) {
|
|
1144
1469
|
if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
|
|
1145
|
-
if (options.
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
annotation: "set by --build-type"
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1470
|
+
if (options.entrypoint) return {
|
|
1471
|
+
key: "bun",
|
|
1472
|
+
buildType: "bun",
|
|
1473
|
+
displayName: "Bun",
|
|
1474
|
+
annotation: "set by --entry"
|
|
1475
|
+
};
|
|
1154
1476
|
const detected = await detectDeployFramework(context.runtime.cwd);
|
|
1155
1477
|
if (detected) return detected;
|
|
1156
1478
|
throw frameworkNotDetectedError(context.runtime.cwd);
|
|
@@ -1165,6 +1487,24 @@ function resolveDeployRuntime(requestedHttpPort, framework) {
|
|
|
1165
1487
|
annotation: `${framework.displayName} default`
|
|
1166
1488
|
};
|
|
1167
1489
|
}
|
|
1490
|
+
function assertSupportedEntrypointForRequestedDeployShape(options) {
|
|
1491
|
+
if (!options.requestedFramework) return;
|
|
1492
|
+
assertSupportedEntrypoint(frameworkFromUserFacingValue(options.requestedFramework, "set by --framework").buildType, options.entrypoint, "deploy");
|
|
1493
|
+
}
|
|
1494
|
+
async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint) {
|
|
1495
|
+
if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
|
|
1496
|
+
const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd));
|
|
1497
|
+
if (packageEntrypoint) return packageEntrypoint;
|
|
1498
|
+
if (framework.key !== "hono") return;
|
|
1499
|
+
const defaultEntrypoint = "src/index.ts";
|
|
1500
|
+
try {
|
|
1501
|
+
await access(path.join(cwd, defaultEntrypoint));
|
|
1502
|
+
return defaultEntrypoint;
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
if (error.code !== "ENOENT") throw error;
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1168
1508
|
async function detectDeployFramework(cwd) {
|
|
1169
1509
|
const packageJson = await readBunPackageJson(cwd);
|
|
1170
1510
|
const nextConfig = await detectNextConfig(cwd);
|
|
@@ -1180,7 +1520,7 @@ async function detectDeployFramework(cwd) {
|
|
|
1180
1520
|
displayName: "Hono",
|
|
1181
1521
|
annotation: "detected from package.json"
|
|
1182
1522
|
};
|
|
1183
|
-
if (
|
|
1523
|
+
if (hasAnyPackageDependency(packageJson, TANSTACK_START_PACKAGES)) return {
|
|
1184
1524
|
key: "tanstack-start",
|
|
1185
1525
|
buildType: "tanstack-start",
|
|
1186
1526
|
displayName: "TanStack Start",
|
|
@@ -1193,7 +1533,8 @@ async function detectNextConfig(cwd) {
|
|
|
1193
1533
|
"next.config.js",
|
|
1194
1534
|
"next.config.mjs",
|
|
1195
1535
|
"next.config.cjs",
|
|
1196
|
-
"next.config.ts"
|
|
1536
|
+
"next.config.ts",
|
|
1537
|
+
"next.config.mts"
|
|
1197
1538
|
]) {
|
|
1198
1539
|
const filePath = path.join(cwd, candidate);
|
|
1199
1540
|
try {
|
|
@@ -1214,6 +1555,9 @@ async function detectNextConfig(cwd) {
|
|
|
1214
1555
|
function hasPackageDependency(packageJson, dependencyName) {
|
|
1215
1556
|
return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
|
|
1216
1557
|
}
|
|
1558
|
+
function hasAnyPackageDependency(packageJson, dependencyNames) {
|
|
1559
|
+
return dependencyNames.some((dependencyName) => hasPackageDependency(packageJson, dependencyName));
|
|
1560
|
+
}
|
|
1217
1561
|
function hasDependency(dependencies, dependencyName) {
|
|
1218
1562
|
return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
|
|
1219
1563
|
}
|
|
@@ -1233,9 +1577,16 @@ function frameworkFromUserFacingValue(value, annotation) {
|
|
|
1233
1577
|
displayName: "Hono",
|
|
1234
1578
|
annotation
|
|
1235
1579
|
};
|
|
1580
|
+
case "bun": return {
|
|
1581
|
+
key: "bun",
|
|
1582
|
+
buildType: "bun",
|
|
1583
|
+
displayName: "Bun",
|
|
1584
|
+
annotation
|
|
1585
|
+
};
|
|
1236
1586
|
case "tanstack":
|
|
1237
1587
|
case "tanstack-start":
|
|
1238
|
-
case "@tanstack/start":
|
|
1588
|
+
case "@tanstack/react-start":
|
|
1589
|
+
case "@tanstack/solid-start": return {
|
|
1239
1590
|
key: "tanstack-start",
|
|
1240
1591
|
buildType: "tanstack-start",
|
|
1241
1592
|
displayName: "TanStack Start",
|
|
@@ -1245,75 +1596,36 @@ function frameworkFromUserFacingValue(value, annotation) {
|
|
|
1245
1596
|
}
|
|
1246
1597
|
}
|
|
1247
1598
|
function frameworkNotDetectedError(cwd, requestedFramework) {
|
|
1248
|
-
const supported = "Next.js, Hono, TanStack Start";
|
|
1599
|
+
const supported = "Next.js, Hono, TanStack Start, Bun";
|
|
1249
1600
|
const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
|
|
1250
1601
|
return new CliError({
|
|
1251
1602
|
code: "FRAMEWORK_NOT_DETECTED",
|
|
1252
1603
|
domain: "app",
|
|
1253
1604
|
summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
|
|
1254
1605
|
why: `Supported Beta frameworks: ${supported}.`,
|
|
1255
|
-
fix: "Add one of these frameworks as a dependency,
|
|
1606
|
+
fix: "Add one of these frameworks as a dependency, pass --framework <nextjs|hono|tanstack-start|bun>, or pass --entry <path> for a Bun app.",
|
|
1256
1607
|
exitCode: 2,
|
|
1257
1608
|
nextSteps: [
|
|
1258
1609
|
"prisma-cli app deploy --framework nextjs",
|
|
1259
1610
|
"prisma-cli app deploy --framework hono",
|
|
1260
|
-
"prisma-cli app deploy --framework tanstack-start"
|
|
1611
|
+
"prisma-cli app deploy --framework tanstack-start",
|
|
1612
|
+
"prisma-cli app deploy --framework bun --entry server.ts",
|
|
1613
|
+
"prisma-cli app deploy --entry server.ts"
|
|
1261
1614
|
]
|
|
1262
1615
|
});
|
|
1263
1616
|
}
|
|
1264
1617
|
async function maybeRenderDeploySetupBlock(context, details) {
|
|
1265
1618
|
if (context.flags.json || context.flags.quiet) return;
|
|
1266
1619
|
const directory = formatDeployDirectory(context.runtime.cwd);
|
|
1267
|
-
|
|
1268
|
-
|
|
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`);
|
|
1620
|
+
const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
|
|
1621
|
+
context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
|
|
1310
1622
|
}
|
|
1311
|
-
function
|
|
1623
|
+
function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
|
|
1312
1624
|
if (context.flags.json || context.flags.quiet) return;
|
|
1313
|
-
context.output.stderr.write(
|
|
1625
|
+
context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
|
|
1314
1626
|
}
|
|
1315
1627
|
async function maybeCustomizeDeploySettings(context, options) {
|
|
1316
|
-
if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.
|
|
1628
|
+
if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitEntrypoint || options.explicitHttpPort || !canPrompt(context)) return {
|
|
1317
1629
|
framework: options.framework,
|
|
1318
1630
|
runtime: options.runtime
|
|
1319
1631
|
};
|
|
@@ -1365,24 +1677,12 @@ async function maybeCustomizeDeploySettings(context, options) {
|
|
|
1365
1677
|
runtime
|
|
1366
1678
|
};
|
|
1367
1679
|
}
|
|
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
1680
|
function frameworkDisplayName(framework) {
|
|
1382
1681
|
switch (framework) {
|
|
1383
1682
|
case "nextjs": return "Next.js";
|
|
1384
1683
|
case "hono": return "Hono";
|
|
1385
1684
|
case "tanstack-start": return "TanStack Start";
|
|
1685
|
+
case "bun": return "Bun";
|
|
1386
1686
|
}
|
|
1387
1687
|
}
|
|
1388
1688
|
function validateDeployHttpPortText(value) {
|
|
@@ -1398,15 +1698,6 @@ function formatDeployDirectory(cwd) {
|
|
|
1398
1698
|
const basename = path.basename(cwd);
|
|
1399
1699
|
return basename ? `./${basename}` : ".";
|
|
1400
1700
|
}
|
|
1401
|
-
function formatLocalDirectory(cwd, env) {
|
|
1402
|
-
const resolved = path.resolve(cwd);
|
|
1403
|
-
const home = env.HOME ? path.resolve(env.HOME) : null;
|
|
1404
|
-
if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
|
|
1405
|
-
const relative = path.relative(home, resolved);
|
|
1406
|
-
return relative ? `~/${relative}` : "~";
|
|
1407
|
-
}
|
|
1408
|
-
return resolved;
|
|
1409
|
-
}
|
|
1410
1701
|
async function readCurrentWorkspaceId(context) {
|
|
1411
1702
|
const state = await context.stateStore.read();
|
|
1412
1703
|
if (state.auth?.workspaceId) return state.auth.workspaceId;
|
|
@@ -1426,7 +1717,10 @@ function getBuildTypeExamples(commandName) {
|
|
|
1426
1717
|
});
|
|
1427
1718
|
}
|
|
1428
1719
|
function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
|
|
1429
|
-
if (buildType !== "auto" && buildType !== "bun" && entrypoint)
|
|
1720
|
+
if (buildType !== "auto" && buildType !== "bun" && entrypoint) {
|
|
1721
|
+
if (commandName === "deploy") throw usageError(`App deploy does not accept --entry with ${formatBuildTypeName(buildType)}`, `${formatBuildTypeName(buildType)} apps derive their runtime entrypoint from build output.`, "Remove --entry, or use --framework bun when you want to target a Bun entrypoint directly.", [`prisma-cli app deploy --framework ${buildType}`, "prisma-cli app deploy --framework bun --entry server.ts"], "app");
|
|
1722
|
+
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");
|
|
1723
|
+
}
|
|
1430
1724
|
}
|
|
1431
1725
|
async function requireLocalBuildType(context, buildType, commandName) {
|
|
1432
1726
|
const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
|
|
@@ -1467,24 +1761,41 @@ function deployFailedError(summary, error, nextSteps) {
|
|
|
1467
1761
|
function appDeployFailedError(error, progress) {
|
|
1468
1762
|
const why = error instanceof Error ? error.message : String(error);
|
|
1469
1763
|
const debug = formatDebugDetails(error);
|
|
1470
|
-
if (progress.buildStarted && !progress.buildCompleted)
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
"",
|
|
1481
|
-
|
|
1482
|
-
"",
|
|
1483
|
-
|
|
1484
|
-
]
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1764
|
+
if (progress.buildStarted && !progress.buildCompleted) {
|
|
1765
|
+
const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
|
|
1766
|
+
const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
|
|
1767
|
+
const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
|
|
1768
|
+
const nextActions = standaloneOutputFailure ? [{
|
|
1769
|
+
kind: "edit-file",
|
|
1770
|
+
journey: "deploy-app",
|
|
1771
|
+
label: "Add Next.js standalone output",
|
|
1772
|
+
reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
|
|
1773
|
+
}, {
|
|
1774
|
+
kind: "run-command",
|
|
1775
|
+
journey: "deploy-app",
|
|
1776
|
+
label: "Rerun deploy",
|
|
1777
|
+
command: "prisma-cli app deploy"
|
|
1778
|
+
}] : [];
|
|
1779
|
+
return new CliError({
|
|
1780
|
+
code: "BUILD_FAILED",
|
|
1781
|
+
domain: "app",
|
|
1782
|
+
summary: "Build failed locally.",
|
|
1783
|
+
why,
|
|
1784
|
+
fix,
|
|
1785
|
+
debug,
|
|
1786
|
+
meta: { phase: "build" },
|
|
1787
|
+
humanLines: [
|
|
1788
|
+
"Build failed locally.",
|
|
1789
|
+
"",
|
|
1790
|
+
`✗ Built ${why}`,
|
|
1791
|
+
"",
|
|
1792
|
+
`Fix: ${fix}`
|
|
1793
|
+
],
|
|
1794
|
+
exitCode: 1,
|
|
1795
|
+
nextSteps,
|
|
1796
|
+
nextActions
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1488
1799
|
if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
|
|
1489
1800
|
const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
|
|
1490
1801
|
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."];
|
|
@@ -1531,61 +1842,52 @@ function localResolutionPinStaleError() {
|
|
|
1531
1842
|
domain: "project",
|
|
1532
1843
|
summary: "Local project binding is stale",
|
|
1533
1844
|
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}
|
|
1845
|
+
fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
|
|
1535
1846
|
meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
|
|
1536
1847
|
exitCode: 1,
|
|
1537
|
-
nextSteps: [
|
|
1848
|
+
nextSteps: [
|
|
1849
|
+
"prisma-cli project list",
|
|
1850
|
+
"prisma-cli project link <id-or-name>",
|
|
1851
|
+
"prisma-cli app deploy --project <id-or-name>"
|
|
1852
|
+
]
|
|
1538
1853
|
});
|
|
1539
1854
|
}
|
|
1540
1855
|
function readDeployEnvOverride(context, name) {
|
|
1541
1856
|
const value = context.runtime.env[name]?.trim();
|
|
1542
1857
|
return value ? value : void 0;
|
|
1543
1858
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
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
|
-
});
|
|
1859
|
+
function projectSetupRequiredError(projects, suggestedName) {
|
|
1860
|
+
const createCommand = `prisma-cli app deploy --create-project ${formatCommandArgument(suggestedName.name)}`;
|
|
1568
1861
|
return new CliError({
|
|
1569
|
-
code: "
|
|
1570
|
-
domain: "
|
|
1571
|
-
summary: "
|
|
1572
|
-
why:
|
|
1573
|
-
fix: "
|
|
1574
|
-
|
|
1862
|
+
code: "PROJECT_SETUP_REQUIRED",
|
|
1863
|
+
domain: "project",
|
|
1864
|
+
summary: "Choose a Project before deploying this directory",
|
|
1865
|
+
why: "This directory is not linked to a Prisma Project, and deploy will not choose or create one implicitly.",
|
|
1866
|
+
fix: "Choose an existing Project with --project, create one with --create-project, or rerun interactively to pick from the setup list.",
|
|
1867
|
+
meta: {
|
|
1868
|
+
candidates: sortProjects(projects).map((project) => ({
|
|
1869
|
+
id: project.id,
|
|
1870
|
+
name: project.name
|
|
1871
|
+
})),
|
|
1872
|
+
suggestedProjectName: suggestedName.name,
|
|
1873
|
+
suggestedProjectNameSource: suggestedName.source,
|
|
1874
|
+
recoveryCommands: ["prisma-cli app deploy --project <id-or-name>", createCommand]
|
|
1875
|
+
},
|
|
1575
1876
|
exitCode: 1,
|
|
1576
|
-
nextSteps
|
|
1877
|
+
nextSteps: [
|
|
1878
|
+
"prisma-cli project list",
|
|
1879
|
+
"prisma-cli app deploy --project <id-or-name>",
|
|
1880
|
+
createCommand
|
|
1881
|
+
],
|
|
1882
|
+
nextActions: buildProjectSetupNextActions({
|
|
1883
|
+
commandName: "app deploy",
|
|
1884
|
+
createCommand,
|
|
1885
|
+
reason: "This directory is not linked to a Prisma Project. Ask the user which Project to use before deploying; package and directory names are setup suggestions only."
|
|
1886
|
+
})
|
|
1577
1887
|
});
|
|
1578
1888
|
}
|
|
1579
|
-
function
|
|
1580
|
-
|
|
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;
|
|
1889
|
+
function isNextStandaloneOutputFailure(message) {
|
|
1890
|
+
return /next\.?js/i.test(message) && /standalone output/i.test(message);
|
|
1589
1891
|
}
|
|
1590
1892
|
function noDeploymentsError(summary, why) {
|
|
1591
1893
|
return new CliError({
|
|
@@ -1671,21 +1973,5 @@ function sortApps(apps) {
|
|
|
1671
1973
|
function toOptionalEnvVars(envVars) {
|
|
1672
1974
|
return Object.keys(envVars).length > 0 ? envVars : void 0;
|
|
1673
1975
|
}
|
|
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
|
-
}
|
|
1690
1976
|
//#endregion
|
|
1691
|
-
export { runAppBuild, runAppDeploy,
|
|
1977
|
+
export { runAppBuild, runAppDeploy, runAppDomainAdd, runAppDomainRemove, runAppDomainRetry, runAppDomainShow, runAppDomainWait, runAppListDeploys, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy };
|