@prisma/cli 3.0.0-dev.38.1 → 3.0.0-dev.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/app/index.js +6 -5
- package/dist/commands/project/index.js +28 -2
- package/dist/controllers/app.js +237 -221
- package/dist/controllers/project.js +47 -2
- package/dist/lib/project/resolution.js +1 -1
- package/dist/lib/project/setup.js +86 -0
- package/dist/presenters/project.js +10 -1
- package/dist/shell/command-meta.js +29 -2
- package/package.json +1 -1
|
@@ -59,26 +59,27 @@ function createRunCommand(runtime) {
|
|
|
59
59
|
}
|
|
60
60
|
function createDeployCommand(runtime) {
|
|
61
61
|
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("deploy"), runtime), "app.deploy");
|
|
62
|
-
command.addOption(new Option("--app <name>", "App name")).addOption(new Option("--project <id-or-name>", "Project id or name")).addOption(new Option("--branch <name>", "Branch name")).addOption(new Option("--framework <name>", "Framework to deploy").choices([
|
|
62
|
+
command.addOption(new Option("--app <name>", "App name")).addOption(new Option("--project <id-or-name>", "Project id or name")).addOption(new Option("--create-project <name>", "Create and link a new Project before deploying")).addOption(new Option("--branch <name>", "Branch name")).addOption(new Option("--framework <name>", "Framework to deploy").choices([
|
|
63
63
|
"nextjs",
|
|
64
64
|
"hono",
|
|
65
|
-
"tanstack-start"
|
|
66
|
-
|
|
65
|
+
"tanstack-start",
|
|
66
|
+
"bun"
|
|
67
|
+
])).addOption(new Option("--entry <path>", "Entrypoint path for Bun deploys")).addOption(new Option("--http-port <port>", "HTTP port override for the deployed app")).addOption(new Option("--env <name=value>", "Environment variable").argParser(collectRepeatableValues));
|
|
67
68
|
addGlobalFlags(command);
|
|
68
69
|
command.action(async (options) => {
|
|
69
70
|
const appName = options.app;
|
|
70
71
|
const entry = options.entry;
|
|
71
|
-
const buildType = options.buildType;
|
|
72
72
|
const branchName = options.branch;
|
|
73
73
|
const framework = options.framework;
|
|
74
74
|
const httpPort = options.httpPort;
|
|
75
75
|
const envAssignments = options.env;
|
|
76
76
|
const projectRef = options.project;
|
|
77
|
+
const createProjectName = options.createProject;
|
|
77
78
|
await runCommand(runtime, "app.deploy", options, (context) => runAppDeploy(context, appName, {
|
|
78
79
|
projectRef,
|
|
80
|
+
createProjectName,
|
|
79
81
|
branchName,
|
|
80
82
|
entrypoint: entry,
|
|
81
|
-
buildType,
|
|
82
83
|
framework,
|
|
83
84
|
httpPort,
|
|
84
85
|
envAssignments
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { attachCommandDescriptor } from "../../shell/command-meta.js";
|
|
2
2
|
import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags.js";
|
|
3
3
|
import { configureRuntimeCommand } from "../../shell/runtime.js";
|
|
4
|
-
import { runProjectList, runProjectShow } from "../../controllers/project.js";
|
|
4
|
+
import { runProjectCreate, runProjectLink, runProjectList, runProjectShow } from "../../controllers/project.js";
|
|
5
5
|
import { runCommand } from "../../shell/command-runner.js";
|
|
6
|
-
import { renderProjectList, renderProjectShow, serializeProjectList, serializeProjectShow } from "../../presenters/project.js";
|
|
6
|
+
import { renderProjectList, renderProjectSetup, renderProjectShow, serializeProjectList, serializeProjectSetup, serializeProjectShow } from "../../presenters/project.js";
|
|
7
7
|
import { createEnvCommand } from "../env.js";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
//#region src/commands/project/index.ts
|
|
@@ -12,9 +12,35 @@ function createProjectCommand(runtime) {
|
|
|
12
12
|
addCompactGlobalFlags(project);
|
|
13
13
|
project.addCommand(createProjectListCommand(runtime));
|
|
14
14
|
project.addCommand(createProjectShowCommand(runtime));
|
|
15
|
+
project.addCommand(createProjectCreateCommand(runtime));
|
|
16
|
+
project.addCommand(createProjectLinkCommand(runtime));
|
|
15
17
|
project.addCommand(createEnvCommand(runtime));
|
|
16
18
|
return project;
|
|
17
19
|
}
|
|
20
|
+
function createProjectCreateCommand(runtime) {
|
|
21
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("create"), runtime), "project.create");
|
|
22
|
+
command.argument("<name>", "Project name");
|
|
23
|
+
addGlobalFlags(command);
|
|
24
|
+
command.action(async (name, options) => {
|
|
25
|
+
await runCommand(runtime, "project.create", options, (context) => runProjectCreate(context, String(name)), {
|
|
26
|
+
renderHuman: (context, descriptor, result) => renderProjectSetup(context, descriptor, result),
|
|
27
|
+
renderJson: (result) => serializeProjectSetup(result)
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
return command;
|
|
31
|
+
}
|
|
32
|
+
function createProjectLinkCommand(runtime) {
|
|
33
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("link"), runtime), "project.link");
|
|
34
|
+
command.argument("<id-or-name>", "Project id or name");
|
|
35
|
+
addGlobalFlags(command);
|
|
36
|
+
command.action(async (projectRef, options) => {
|
|
37
|
+
await runCommand(runtime, "project.link", options, (context) => runProjectLink(context, String(projectRef)), {
|
|
38
|
+
renderHuman: (context, descriptor, result) => renderProjectSetup(context, descriptor, result),
|
|
39
|
+
renderJson: (result) => serializeProjectSetup(result)
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
return command;
|
|
43
|
+
}
|
|
18
44
|
function createProjectListCommand(runtime) {
|
|
19
45
|
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "project.list");
|
|
20
46
|
addGlobalFlags(command);
|
package/dist/controllers/app.js
CHANGED
|
@@ -9,10 +9,11 @@ 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 { inferTargetName, projectNotFoundError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
15
|
-
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
14
|
+
import { inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
15
|
+
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
16
|
+
import { bindProjectToDirectory, formatCommandArgument, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
16
17
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
17
18
|
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
18
19
|
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
|
|
@@ -28,8 +29,10 @@ import open from "open";
|
|
|
28
29
|
const DEPLOY_FRAMEWORKS = [
|
|
29
30
|
"nextjs",
|
|
30
31
|
"hono",
|
|
31
|
-
"tanstack-start"
|
|
32
|
+
"tanstack-start",
|
|
33
|
+
"bun"
|
|
32
34
|
];
|
|
35
|
+
const TANSTACK_START_PACKAGES = ["@tanstack/react-start", "@tanstack/solid-start"];
|
|
33
36
|
const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
|
|
34
37
|
const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
|
|
35
38
|
const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
|
|
@@ -96,67 +99,65 @@ async function runAppDeploy(context, appName, options) {
|
|
|
96
99
|
ensurePreviewAppMode(context);
|
|
97
100
|
const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
|
|
98
101
|
const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
|
|
99
|
-
|
|
102
|
+
assertExclusiveDeployProjectInputs({
|
|
103
|
+
projectRef: options?.projectRef,
|
|
104
|
+
createProjectName: options?.createProjectName,
|
|
105
|
+
envProjectId
|
|
106
|
+
});
|
|
107
|
+
const skipLocalPin = Boolean(envProjectId || options?.projectRef || options?.createProjectName);
|
|
100
108
|
const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
|
|
101
109
|
if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
|
|
102
|
-
const explicitBuildType = Boolean(options?.buildType && options.buildType !== "auto");
|
|
103
110
|
const branch = await resolveDeployBranch(context, options?.branchName);
|
|
104
111
|
if (options?.httpPort) parseDeployHttpPort(options.httpPort);
|
|
105
|
-
|
|
112
|
+
assertSupportedEntrypointForRequestedDeployShape({
|
|
106
113
|
requestedFramework: options?.framework,
|
|
107
|
-
|
|
108
|
-
explicitBuildType
|
|
114
|
+
entrypoint: options?.entrypoint
|
|
109
115
|
});
|
|
110
|
-
let runtime = resolveDeployRuntime(options?.httpPort, framework);
|
|
111
|
-
assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
|
|
112
|
-
const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
|
|
113
|
-
const firstDeploy = !skipLocalPin && localPin.kind === "missing";
|
|
114
116
|
const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
|
|
115
|
-
allowCreate: true,
|
|
116
117
|
branch,
|
|
118
|
+
createProjectName: options?.createProjectName,
|
|
117
119
|
envProjectId,
|
|
118
120
|
localPin
|
|
119
121
|
});
|
|
122
|
+
let localPinResult;
|
|
123
|
+
if (target.localPinAction) {
|
|
124
|
+
const setupResult = await bindProjectToDirectory(context, target.workspace, target.project, target.localPinAction);
|
|
125
|
+
localPinResult = setupResult.localPin;
|
|
126
|
+
maybeRenderProjectLinked(context, setupResult.directory, setupResult.project.name, setupResult.localPin.path);
|
|
127
|
+
}
|
|
128
|
+
let framework = await resolveDeployFramework(context, {
|
|
129
|
+
requestedFramework: options?.framework,
|
|
130
|
+
entrypoint: options?.entrypoint
|
|
131
|
+
});
|
|
132
|
+
let runtime = resolveDeployRuntime(options?.httpPort, framework);
|
|
133
|
+
assertSupportedEntrypoint(framework.buildType, options?.entrypoint, "deploy");
|
|
134
|
+
const envVars = toOptionalEnvVars(parseEnvAssignments(options?.envAssignments, { commandName: "deploy" }));
|
|
120
135
|
const selectedApp = await resolveDeployAppSelection(context, projectId, await listApps(context, provider, projectId, target.branch.name), {
|
|
121
136
|
explicitAppName: appName,
|
|
122
137
|
explicitAppId: envAppId,
|
|
123
|
-
firstDeploy,
|
|
138
|
+
firstDeploy: Boolean(target.localPinAction),
|
|
124
139
|
inferName: () => inferTargetName(context.runtime.cwd)
|
|
125
140
|
});
|
|
126
141
|
await maybeRenderDeploySetupBlock(context, {
|
|
127
|
-
|
|
128
|
-
workspaceName: target.workspace.name,
|
|
142
|
+
includeDirectory: !target.localPinAction,
|
|
129
143
|
projectName: target.project.name,
|
|
130
|
-
projectAnnotation: annotationForProjectResolution(target.resolution),
|
|
131
144
|
branchName: target.branch.name,
|
|
132
|
-
|
|
133
|
-
appName: selectedApp.displayName,
|
|
134
|
-
appAnnotation: selectedApp.annotation,
|
|
135
|
-
framework,
|
|
136
|
-
runtime
|
|
145
|
+
appName: selectedApp.displayName
|
|
137
146
|
});
|
|
138
147
|
const customized = await maybeCustomizeDeploySettings(context, {
|
|
139
148
|
framework,
|
|
140
149
|
runtime,
|
|
141
150
|
firstDeploy: selectedApp.firstDeploy,
|
|
142
151
|
explicitFramework: Boolean(options?.framework),
|
|
143
|
-
|
|
152
|
+
explicitEntrypoint: Boolean(options?.entrypoint),
|
|
144
153
|
explicitHttpPort: Boolean(options?.httpPort)
|
|
145
154
|
});
|
|
146
155
|
framework = customized.framework;
|
|
147
156
|
runtime = customized.runtime;
|
|
148
157
|
const buildType = framework.buildType;
|
|
149
158
|
assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
|
|
159
|
+
const entrypoint = await resolveDeployEntrypoint(context.runtime.cwd, framework, options?.entrypoint);
|
|
150
160
|
const portMapping = parseDeployPortMapping(String(runtime.port));
|
|
151
|
-
const shouldWriteLocalPin = firstDeploy && !skipLocalPin;
|
|
152
|
-
if (shouldWriteLocalPin) {
|
|
153
|
-
await writeLocalResolutionPin(context.runtime.cwd, {
|
|
154
|
-
workspaceId: target.workspace.id,
|
|
155
|
-
projectId: target.project.id
|
|
156
|
-
});
|
|
157
|
-
await ensureLocalResolutionPinGitignore(context.runtime.cwd);
|
|
158
|
-
maybeRenderLocalPinBound(context, target.project.name);
|
|
159
|
-
}
|
|
160
161
|
const progressState = createPreviewDeployProgressState();
|
|
161
162
|
const deployStartedAt = Date.now();
|
|
162
163
|
const deployResult = await provider.deployApp({
|
|
@@ -166,7 +167,7 @@ async function runAppDeploy(context, appName, options) {
|
|
|
166
167
|
appId: selectedApp.appId,
|
|
167
168
|
appName: selectedApp.appName,
|
|
168
169
|
region: selectedApp.region,
|
|
169
|
-
entrypoint
|
|
170
|
+
entrypoint,
|
|
170
171
|
buildType,
|
|
171
172
|
portMapping,
|
|
172
173
|
envVars,
|
|
@@ -194,10 +195,7 @@ async function runAppDeploy(context, appName, options) {
|
|
|
194
195
|
},
|
|
195
196
|
deployment: deployResult.deployment,
|
|
196
197
|
durationMs: deployDurationMs,
|
|
197
|
-
localPin:
|
|
198
|
-
path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
199
|
-
written: true
|
|
200
|
-
} : void 0
|
|
198
|
+
localPin: localPinResult
|
|
201
199
|
},
|
|
202
200
|
warnings: [],
|
|
203
201
|
nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
|
|
@@ -719,11 +717,10 @@ async function resolveAppDomainTarget(context, options) {
|
|
|
719
717
|
});
|
|
720
718
|
const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
|
|
721
719
|
const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
|
|
722
|
-
const skipLocalPin = Boolean(envProjectId ||
|
|
720
|
+
const skipLocalPin = Boolean(envProjectId || options?.projectRef);
|
|
723
721
|
const localPin = skipLocalPin ? { kind: "missing" } : await readLocalResolutionPin(context.runtime.cwd);
|
|
724
722
|
if (!skipLocalPin && localPin.kind === "invalid") throw localResolutionPinStaleError();
|
|
725
723
|
const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
|
|
726
|
-
allowCreate: false,
|
|
727
724
|
branch,
|
|
728
725
|
envProjectId,
|
|
729
726
|
localPin
|
|
@@ -1258,7 +1255,7 @@ function createPreviewLogAuthOptions(env) {
|
|
|
1258
1255
|
}
|
|
1259
1256
|
async function requireProviderAndProjectContext(context, explicitProject, options) {
|
|
1260
1257
|
const { client, provider } = await requirePreviewAppProviderWithClient(context);
|
|
1261
|
-
const target = await resolveProjectContext(context, client,
|
|
1258
|
+
const target = await resolveProjectContext(context, client, explicitProject, options);
|
|
1262
1259
|
return {
|
|
1263
1260
|
client,
|
|
1264
1261
|
provider,
|
|
@@ -1276,7 +1273,7 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
|
|
|
1276
1273
|
projectId: target.project.id
|
|
1277
1274
|
};
|
|
1278
1275
|
}
|
|
1279
|
-
async function resolveProjectContext(context, client,
|
|
1276
|
+
async function resolveProjectContext(context, client, explicitProject, options) {
|
|
1280
1277
|
const authState = await requireAuthenticatedAuthState(context);
|
|
1281
1278
|
if (!authState.workspace) throw workspaceRequiredError();
|
|
1282
1279
|
const resolved = await resolveProjectTarget({
|
|
@@ -1284,22 +1281,6 @@ async function resolveProjectContext(context, client, provider, explicitProject,
|
|
|
1284
1281
|
workspace: authState.workspace,
|
|
1285
1282
|
explicitProject,
|
|
1286
1283
|
listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
|
|
1287
|
-
createProject: options?.allowCreate ? async (name) => {
|
|
1288
|
-
const project = await provider.createProject({ name }).catch((error) => {
|
|
1289
|
-
throw createProjectOnFirstDeployError({
|
|
1290
|
-
error,
|
|
1291
|
-
inferredName: name,
|
|
1292
|
-
workspaceName: authState.workspace.name
|
|
1293
|
-
});
|
|
1294
|
-
});
|
|
1295
|
-
return {
|
|
1296
|
-
id: project.id,
|
|
1297
|
-
name: project.name,
|
|
1298
|
-
workspace: authState.workspace
|
|
1299
|
-
};
|
|
1300
|
-
} : void 0,
|
|
1301
|
-
allowCreate: options?.allowCreate,
|
|
1302
|
-
prompt: createSelectPromptPort(context),
|
|
1303
1284
|
remember: true
|
|
1304
1285
|
});
|
|
1305
1286
|
const branch = options?.branch ?? await resolveDeployBranch(context, void 0);
|
|
@@ -1316,30 +1297,30 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1316
1297
|
if (!workspace) throw workspaceRequiredError();
|
|
1317
1298
|
const branch = options.branch ?? await resolveDeployBranch(context, void 0);
|
|
1318
1299
|
const projects = await listRealWorkspaceProjects(client, workspace);
|
|
1319
|
-
|
|
1320
|
-
const project = await provider.createProject({ name }).catch((error) => {
|
|
1321
|
-
throw createProjectOnFirstDeployError({
|
|
1322
|
-
error,
|
|
1323
|
-
inferredName: name,
|
|
1324
|
-
workspaceName: workspace.name
|
|
1325
|
-
});
|
|
1326
|
-
});
|
|
1327
|
-
return {
|
|
1328
|
-
id: project.id,
|
|
1329
|
-
name: project.name,
|
|
1330
|
-
workspace
|
|
1331
|
-
};
|
|
1332
|
-
} : void 0;
|
|
1333
|
-
if (explicitProject) return withDeployBranch(await resolveProjectTarget({
|
|
1334
|
-
context,
|
|
1300
|
+
if (explicitProject) return withDeployBranch({
|
|
1335
1301
|
workspace,
|
|
1336
|
-
explicitProject,
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
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
|
+
}
|
|
1343
1324
|
if (options.envProjectId) {
|
|
1344
1325
|
const project = projects.find((candidate) => candidate.id === options.envProjectId);
|
|
1345
1326
|
if (!project) throw projectNotFoundError(options.envProjectId, workspace);
|
|
@@ -1368,15 +1349,91 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1368
1349
|
}
|
|
1369
1350
|
}, branch);
|
|
1370
1351
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1352
|
+
const platformMapping = await resolveDurablePlatformMapping();
|
|
1353
|
+
if (platformMapping && platformMapping.workspace.id === workspace.id) return withDeployBranch({
|
|
1373
1354
|
workspace,
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
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 sortedProjects = sortProjects(projects);
|
|
1367
|
+
const choice = await selectPrompt({
|
|
1368
|
+
input: context.runtime.stdin,
|
|
1369
|
+
output: context.runtime.stderr,
|
|
1370
|
+
message: "Which Project should this directory use?",
|
|
1371
|
+
choices: [
|
|
1372
|
+
...sortedProjects.map((project) => ({
|
|
1373
|
+
label: project.name,
|
|
1374
|
+
value: {
|
|
1375
|
+
kind: "project",
|
|
1376
|
+
project
|
|
1377
|
+
}
|
|
1378
|
+
})),
|
|
1379
|
+
{
|
|
1380
|
+
label: "Create a new Project",
|
|
1381
|
+
value: { kind: "create" }
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
label: "Cancel",
|
|
1385
|
+
value: { kind: "cancel" }
|
|
1386
|
+
}
|
|
1387
|
+
]
|
|
1388
|
+
});
|
|
1389
|
+
if (choice.kind === "cancel") throw usageError("Project setup canceled", "Deploy needs a Project before it can continue.", "Choose an existing Project or create a new one, then rerun deploy.", ["prisma-cli app deploy --project <id-or-name>", "prisma-cli app deploy --create-project <name>"], "project");
|
|
1390
|
+
if (choice.kind === "project") return {
|
|
1391
|
+
workspace,
|
|
1392
|
+
project: toProjectSummary(choice.project),
|
|
1393
|
+
resolution: {
|
|
1394
|
+
projectSource: "prompt",
|
|
1395
|
+
targetName: choice.project.name,
|
|
1396
|
+
targetNameSource: "prompt"
|
|
1397
|
+
},
|
|
1398
|
+
localPinAction: "linked"
|
|
1399
|
+
};
|
|
1400
|
+
const suggestedName = await inferTargetName(context.runtime.cwd);
|
|
1401
|
+
const rawName = await textPrompt({
|
|
1402
|
+
input: context.runtime.stdin,
|
|
1403
|
+
output: context.runtime.stderr,
|
|
1404
|
+
message: "Project name",
|
|
1405
|
+
placeholder: suggestedName.name,
|
|
1406
|
+
validate: (value) => validateProjectSetupNameText(value, suggestedName.name)
|
|
1407
|
+
});
|
|
1408
|
+
const projectName = rawName.trim() || suggestedName.name;
|
|
1409
|
+
return {
|
|
1410
|
+
workspace,
|
|
1411
|
+
project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace)),
|
|
1412
|
+
resolution: {
|
|
1413
|
+
projectSource: "created",
|
|
1414
|
+
targetName: projectName,
|
|
1415
|
+
targetNameSource: rawName.trim() ? "prompt" : suggestedName.source
|
|
1416
|
+
},
|
|
1417
|
+
localPinAction: "created"
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
async function createProjectForDeploySetup(provider, projectName, workspace) {
|
|
1421
|
+
const created = await provider.createProject({ name: projectName }).catch((error) => {
|
|
1422
|
+
throw projectCreateFailedError(error, projectName, workspace, {
|
|
1423
|
+
nextSteps: [
|
|
1424
|
+
"prisma-cli project list",
|
|
1425
|
+
"prisma-cli app deploy --project <id-or-name>",
|
|
1426
|
+
`prisma-cli app deploy --create-project ${formatCommandArgument(projectName)}`
|
|
1427
|
+
],
|
|
1428
|
+
permissionFix: "Choose an existing Project with --project, or grant the token permission to create Projects in this workspace.",
|
|
1429
|
+
fallbackFix: "Choose an existing Project with --project, or retry after addressing the platform error above."
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
return {
|
|
1433
|
+
id: created.id,
|
|
1434
|
+
name: created.name,
|
|
1435
|
+
workspace
|
|
1436
|
+
};
|
|
1380
1437
|
}
|
|
1381
1438
|
function withDeployBranch(target, branch) {
|
|
1382
1439
|
return {
|
|
@@ -1387,15 +1444,26 @@ function withDeployBranch(target, branch) {
|
|
|
1387
1444
|
}
|
|
1388
1445
|
};
|
|
1389
1446
|
}
|
|
1390
|
-
function toProjectSummary(project) {
|
|
1391
|
-
return {
|
|
1392
|
-
id: project.id,
|
|
1393
|
-
name: project.name
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
1447
|
function toBranchKind(name) {
|
|
1397
1448
|
return name === "production" || name === "main" ? "production" : "preview";
|
|
1398
1449
|
}
|
|
1450
|
+
function assertExclusiveDeployProjectInputs(options) {
|
|
1451
|
+
const provided = [
|
|
1452
|
+
options.projectRef ? "--project" : null,
|
|
1453
|
+
options.createProjectName ? "--create-project" : null,
|
|
1454
|
+
options.envProjectId ? PRISMA_PROJECT_ID_ENV_VAR : null
|
|
1455
|
+
].filter((value) => Boolean(value));
|
|
1456
|
+
if (provided.length <= 1) return;
|
|
1457
|
+
throw usageError("Project selection is ambiguous", `${provided.join(", ")} cannot be used together.`, "Choose exactly one Project source for this deploy.", [
|
|
1458
|
+
"prisma-cli app deploy --project <id-or-name>",
|
|
1459
|
+
"prisma-cli app deploy --create-project <name>",
|
|
1460
|
+
`unset ${PRISMA_PROJECT_ID_ENV_VAR}`
|
|
1461
|
+
], "project");
|
|
1462
|
+
}
|
|
1463
|
+
function validateProjectSetupNameText(value, fallback) {
|
|
1464
|
+
if ((value?.trim() || fallback).trim().length > 0) return;
|
|
1465
|
+
return "Enter a Project name.";
|
|
1466
|
+
}
|
|
1399
1467
|
async function resolveDeployBranch(context, explicitBranchName) {
|
|
1400
1468
|
if (explicitBranchName) return {
|
|
1401
1469
|
name: explicitBranchName,
|
|
@@ -1436,15 +1504,12 @@ async function resolveGitHeadPath(gitPath) {
|
|
|
1436
1504
|
}
|
|
1437
1505
|
async function resolveDeployFramework(context, options) {
|
|
1438
1506
|
if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
|
|
1439
|
-
if (options.
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
annotation: "set by --build-type"
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1507
|
+
if (options.entrypoint) return {
|
|
1508
|
+
key: "bun",
|
|
1509
|
+
buildType: "bun",
|
|
1510
|
+
displayName: "Bun",
|
|
1511
|
+
annotation: "set by --entry"
|
|
1512
|
+
};
|
|
1448
1513
|
const detected = await detectDeployFramework(context.runtime.cwd);
|
|
1449
1514
|
if (detected) return detected;
|
|
1450
1515
|
throw frameworkNotDetectedError(context.runtime.cwd);
|
|
@@ -1459,6 +1524,24 @@ function resolveDeployRuntime(requestedHttpPort, framework) {
|
|
|
1459
1524
|
annotation: `${framework.displayName} default`
|
|
1460
1525
|
};
|
|
1461
1526
|
}
|
|
1527
|
+
function assertSupportedEntrypointForRequestedDeployShape(options) {
|
|
1528
|
+
if (!options.requestedFramework) return;
|
|
1529
|
+
assertSupportedEntrypoint(frameworkFromUserFacingValue(options.requestedFramework, "set by --framework").buildType, options.entrypoint, "deploy");
|
|
1530
|
+
}
|
|
1531
|
+
async function resolveDeployEntrypoint(cwd, framework, explicitEntrypoint) {
|
|
1532
|
+
if (explicitEntrypoint || framework.buildType !== "bun") return explicitEntrypoint;
|
|
1533
|
+
const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd));
|
|
1534
|
+
if (packageEntrypoint) return packageEntrypoint;
|
|
1535
|
+
if (framework.key !== "hono") return;
|
|
1536
|
+
const defaultEntrypoint = "src/index.ts";
|
|
1537
|
+
try {
|
|
1538
|
+
await access(path.join(cwd, defaultEntrypoint));
|
|
1539
|
+
return defaultEntrypoint;
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
if (error.code !== "ENOENT") throw error;
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1462
1545
|
async function detectDeployFramework(cwd) {
|
|
1463
1546
|
const packageJson = await readBunPackageJson(cwd);
|
|
1464
1547
|
const nextConfig = await detectNextConfig(cwd);
|
|
@@ -1474,7 +1557,7 @@ async function detectDeployFramework(cwd) {
|
|
|
1474
1557
|
displayName: "Hono",
|
|
1475
1558
|
annotation: "detected from package.json"
|
|
1476
1559
|
};
|
|
1477
|
-
if (
|
|
1560
|
+
if (hasAnyPackageDependency(packageJson, TANSTACK_START_PACKAGES)) return {
|
|
1478
1561
|
key: "tanstack-start",
|
|
1479
1562
|
buildType: "tanstack-start",
|
|
1480
1563
|
displayName: "TanStack Start",
|
|
@@ -1487,7 +1570,8 @@ async function detectNextConfig(cwd) {
|
|
|
1487
1570
|
"next.config.js",
|
|
1488
1571
|
"next.config.mjs",
|
|
1489
1572
|
"next.config.cjs",
|
|
1490
|
-
"next.config.ts"
|
|
1573
|
+
"next.config.ts",
|
|
1574
|
+
"next.config.mts"
|
|
1491
1575
|
]) {
|
|
1492
1576
|
const filePath = path.join(cwd, candidate);
|
|
1493
1577
|
try {
|
|
@@ -1508,6 +1592,9 @@ async function detectNextConfig(cwd) {
|
|
|
1508
1592
|
function hasPackageDependency(packageJson, dependencyName) {
|
|
1509
1593
|
return hasDependency(packageJson?.dependencies, dependencyName) || hasDependency(packageJson?.devDependencies, dependencyName);
|
|
1510
1594
|
}
|
|
1595
|
+
function hasAnyPackageDependency(packageJson, dependencyNames) {
|
|
1596
|
+
return dependencyNames.some((dependencyName) => hasPackageDependency(packageJson, dependencyName));
|
|
1597
|
+
}
|
|
1511
1598
|
function hasDependency(dependencies, dependencyName) {
|
|
1512
1599
|
return Boolean(dependencies && typeof dependencies === "object" && dependencyName in dependencies);
|
|
1513
1600
|
}
|
|
@@ -1527,9 +1614,16 @@ function frameworkFromUserFacingValue(value, annotation) {
|
|
|
1527
1614
|
displayName: "Hono",
|
|
1528
1615
|
annotation
|
|
1529
1616
|
};
|
|
1617
|
+
case "bun": return {
|
|
1618
|
+
key: "bun",
|
|
1619
|
+
buildType: "bun",
|
|
1620
|
+
displayName: "Bun",
|
|
1621
|
+
annotation
|
|
1622
|
+
};
|
|
1530
1623
|
case "tanstack":
|
|
1531
1624
|
case "tanstack-start":
|
|
1532
|
-
case "@tanstack/start":
|
|
1625
|
+
case "@tanstack/react-start":
|
|
1626
|
+
case "@tanstack/solid-start": return {
|
|
1533
1627
|
key: "tanstack-start",
|
|
1534
1628
|
buildType: "tanstack-start",
|
|
1535
1629
|
displayName: "TanStack Start",
|
|
@@ -1539,75 +1633,36 @@ function frameworkFromUserFacingValue(value, annotation) {
|
|
|
1539
1633
|
}
|
|
1540
1634
|
}
|
|
1541
1635
|
function frameworkNotDetectedError(cwd, requestedFramework) {
|
|
1542
|
-
const supported = "Next.js, Hono, TanStack Start";
|
|
1636
|
+
const supported = "Next.js, Hono, TanStack Start, Bun";
|
|
1543
1637
|
const directory = cwd ? ` in ${formatDeployDirectory(cwd)}` : "";
|
|
1544
1638
|
return new CliError({
|
|
1545
1639
|
code: "FRAMEWORK_NOT_DETECTED",
|
|
1546
1640
|
domain: "app",
|
|
1547
1641
|
summary: requestedFramework ? `Unsupported framework "${requestedFramework}"` : `Cannot detect a supported framework${directory}`,
|
|
1548
1642
|
why: `Supported Beta frameworks: ${supported}.`,
|
|
1549
|
-
fix: "Add one of these frameworks as a dependency,
|
|
1643
|
+
fix: "Add one of these frameworks as a dependency, pass --framework <nextjs|hono|tanstack-start|bun>, or pass --entry <path> for a Bun app.",
|
|
1550
1644
|
exitCode: 2,
|
|
1551
1645
|
nextSteps: [
|
|
1552
1646
|
"prisma-cli app deploy --framework nextjs",
|
|
1553
1647
|
"prisma-cli app deploy --framework hono",
|
|
1554
|
-
"prisma-cli app deploy --framework tanstack-start"
|
|
1648
|
+
"prisma-cli app deploy --framework tanstack-start",
|
|
1649
|
+
"prisma-cli app deploy --framework bun --entry server.ts",
|
|
1650
|
+
"prisma-cli app deploy --entry server.ts"
|
|
1555
1651
|
]
|
|
1556
1652
|
});
|
|
1557
1653
|
}
|
|
1558
1654
|
async function maybeRenderDeploySetupBlock(context, details) {
|
|
1559
1655
|
if (context.flags.json || context.flags.quiet) return;
|
|
1560
1656
|
const directory = formatDeployDirectory(context.runtime.cwd);
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
return;
|
|
1564
|
-
}
|
|
1565
|
-
const title = `Setting up your local directory ${formatLocalDirectory(context.runtime.cwd, context.runtime.env)}`;
|
|
1566
|
-
const rows = details.firstDeploy ? [
|
|
1567
|
-
{
|
|
1568
|
-
label: "Workspace",
|
|
1569
|
-
value: details.workspaceName
|
|
1570
|
-
},
|
|
1571
|
-
{
|
|
1572
|
-
label: "Project",
|
|
1573
|
-
value: details.projectName,
|
|
1574
|
-
origin: details.projectAnnotation
|
|
1575
|
-
},
|
|
1576
|
-
{
|
|
1577
|
-
label: "Branch",
|
|
1578
|
-
value: details.branchName,
|
|
1579
|
-
origin: details.branchAnnotation
|
|
1580
|
-
},
|
|
1581
|
-
{
|
|
1582
|
-
label: "App",
|
|
1583
|
-
value: details.appName,
|
|
1584
|
-
origin: details.appAnnotation
|
|
1585
|
-
},
|
|
1586
|
-
{
|
|
1587
|
-
label: "Framework",
|
|
1588
|
-
value: details.framework.displayName,
|
|
1589
|
-
origin: details.framework.annotation
|
|
1590
|
-
},
|
|
1591
|
-
{
|
|
1592
|
-
label: "Runtime",
|
|
1593
|
-
value: `HTTP ${details.runtime.port}`,
|
|
1594
|
-
origin: details.runtime.annotation
|
|
1595
|
-
}
|
|
1596
|
-
] : [];
|
|
1597
|
-
const lines = [
|
|
1598
|
-
title,
|
|
1599
|
-
"",
|
|
1600
|
-
...renderDeployOutputRows(context.ui, rows),
|
|
1601
|
-
""
|
|
1602
|
-
];
|
|
1603
|
-
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
1657
|
+
const prefix = details.includeDirectory ? `Deploying ${directory} to` : "Deploying to";
|
|
1658
|
+
context.output.stderr.write(`${prefix} ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
|
|
1604
1659
|
}
|
|
1605
|
-
function
|
|
1660
|
+
function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
|
|
1606
1661
|
if (context.flags.json || context.flags.quiet) return;
|
|
1607
|
-
context.output.stderr.write(
|
|
1662
|
+
context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
|
|
1608
1663
|
}
|
|
1609
1664
|
async function maybeCustomizeDeploySettings(context, options) {
|
|
1610
|
-
if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.
|
|
1665
|
+
if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitEntrypoint || options.explicitHttpPort || !canPrompt(context)) return {
|
|
1611
1666
|
framework: options.framework,
|
|
1612
1667
|
runtime: options.runtime
|
|
1613
1668
|
};
|
|
@@ -1659,24 +1714,12 @@ async function maybeCustomizeDeploySettings(context, options) {
|
|
|
1659
1714
|
runtime
|
|
1660
1715
|
};
|
|
1661
1716
|
}
|
|
1662
|
-
function annotationForProjectResolution(resolution) {
|
|
1663
|
-
switch (resolution.projectSource) {
|
|
1664
|
-
case "explicit": return "set by --project";
|
|
1665
|
-
case "env": return `from ${PRISMA_PROJECT_ID_ENV_VAR}`;
|
|
1666
|
-
case "local-pin": return "from local pin";
|
|
1667
|
-
case "created": return resolution.targetNameSource === "directory-name" ? "created from directory name" : "created from package.json";
|
|
1668
|
-
case "package-name":
|
|
1669
|
-
case "directory-name": return "linked to existing project";
|
|
1670
|
-
case "platform-mapping":
|
|
1671
|
-
case "remembered-local": return "linked to existing project";
|
|
1672
|
-
case "prompt": return "selected by you";
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
1717
|
function frameworkDisplayName(framework) {
|
|
1676
1718
|
switch (framework) {
|
|
1677
1719
|
case "nextjs": return "Next.js";
|
|
1678
1720
|
case "hono": return "Hono";
|
|
1679
1721
|
case "tanstack-start": return "TanStack Start";
|
|
1722
|
+
case "bun": return "Bun";
|
|
1680
1723
|
}
|
|
1681
1724
|
}
|
|
1682
1725
|
function validateDeployHttpPortText(value) {
|
|
@@ -1692,15 +1735,6 @@ function formatDeployDirectory(cwd) {
|
|
|
1692
1735
|
const basename = path.basename(cwd);
|
|
1693
1736
|
return basename ? `./${basename}` : ".";
|
|
1694
1737
|
}
|
|
1695
|
-
function formatLocalDirectory(cwd, env) {
|
|
1696
|
-
const resolved = path.resolve(cwd);
|
|
1697
|
-
const home = env.HOME ? path.resolve(env.HOME) : null;
|
|
1698
|
-
if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
|
|
1699
|
-
const relative = path.relative(home, resolved);
|
|
1700
|
-
return relative ? `~/${relative}` : "~";
|
|
1701
|
-
}
|
|
1702
|
-
return resolved;
|
|
1703
|
-
}
|
|
1704
1738
|
async function readCurrentWorkspaceId(context) {
|
|
1705
1739
|
const state = await context.stateStore.read();
|
|
1706
1740
|
if (state.auth?.workspaceId) return state.auth.workspaceId;
|
|
@@ -1720,7 +1754,10 @@ function getBuildTypeExamples(commandName) {
|
|
|
1720
1754
|
});
|
|
1721
1755
|
}
|
|
1722
1756
|
function assertSupportedEntrypoint(buildType, entrypoint, commandName) {
|
|
1723
|
-
if (buildType !== "auto" && buildType !== "bun" && entrypoint)
|
|
1757
|
+
if (buildType !== "auto" && buildType !== "bun" && entrypoint) {
|
|
1758
|
+
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");
|
|
1759
|
+
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");
|
|
1760
|
+
}
|
|
1724
1761
|
}
|
|
1725
1762
|
async function requireLocalBuildType(context, buildType, commandName) {
|
|
1726
1763
|
const resolvedBuildType = await resolveLocalBuildType(context.runtime.cwd, buildType);
|
|
@@ -1835,52 +1872,31 @@ function readDeployEnvOverride(context, name) {
|
|
|
1835
1872
|
const value = context.runtime.env[name]?.trim();
|
|
1836
1873
|
return value ? value : void 0;
|
|
1837
1874
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
* existing project matches the package.json name (or the cwd basename as a
|
|
1841
|
-
* fallback). When the create call fails the user often doesn't realise the
|
|
1842
|
-
* CLI was attempting to create a project at all — they thought the deploy
|
|
1843
|
-
* would find an existing project. Surface that context, and recommend the
|
|
1844
|
-
* explicit `--project` flag as the unambiguous way out.
|
|
1845
|
-
*/
|
|
1846
|
-
function createProjectOnFirstDeployError(options) {
|
|
1847
|
-
const { error, inferredName, workspaceName } = options;
|
|
1848
|
-
const status = extractHttpStatus(error);
|
|
1849
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1850
|
-
const inferredContext = `No existing project matched the package.json name \`${inferredName}\`, so the CLI attempted to create one.`;
|
|
1851
|
-
const nextSteps = ["prisma-cli project list", "prisma-cli app deploy --project <id-or-name>"];
|
|
1852
|
-
if (status === 401 || status === 403) return new CliError({
|
|
1853
|
-
code: "AUTH_FORBIDDEN",
|
|
1854
|
-
domain: "auth",
|
|
1855
|
-
summary: "Could not create a new project for this deploy",
|
|
1856
|
-
why: `${inferredContext} The platform rejected the create (HTTP ${status}).`,
|
|
1857
|
-
fix: `Pass --project <id-or-name> to deploy into an existing project, or grant the service token project-create permission on workspace \`${workspaceName}\`.`,
|
|
1858
|
-
debug: formatDebugDetails(error),
|
|
1859
|
-
exitCode: 1,
|
|
1860
|
-
nextSteps
|
|
1861
|
-
});
|
|
1875
|
+
function projectSetupRequiredError(projects, suggestedName) {
|
|
1876
|
+
const createCommand = `prisma-cli app deploy --create-project ${formatCommandArgument(suggestedName.name)}`;
|
|
1862
1877
|
return new CliError({
|
|
1863
|
-
code: "
|
|
1864
|
-
domain: "
|
|
1865
|
-
summary: "
|
|
1866
|
-
why:
|
|
1867
|
-
fix: "
|
|
1868
|
-
|
|
1878
|
+
code: "PROJECT_SETUP_REQUIRED",
|
|
1879
|
+
domain: "project",
|
|
1880
|
+
summary: "Choose a Project before deploying this directory",
|
|
1881
|
+
why: "This directory is not linked to a Prisma Project, and deploy will not choose or create one implicitly.",
|
|
1882
|
+
fix: "Choose an existing Project with --project, create one with --create-project, or rerun interactively to pick from the setup list.",
|
|
1883
|
+
meta: {
|
|
1884
|
+
candidates: sortProjects(projects).map((project) => ({
|
|
1885
|
+
id: project.id,
|
|
1886
|
+
name: project.name
|
|
1887
|
+
})),
|
|
1888
|
+
suggestedProjectName: suggestedName.name,
|
|
1889
|
+
suggestedProjectNameSource: suggestedName.source,
|
|
1890
|
+
recoveryCommands: ["prisma-cli app deploy --project <id-or-name>", createCommand]
|
|
1891
|
+
},
|
|
1869
1892
|
exitCode: 1,
|
|
1870
|
-
nextSteps
|
|
1893
|
+
nextSteps: [
|
|
1894
|
+
"prisma-cli project list",
|
|
1895
|
+
"prisma-cli app deploy --project <id-or-name>",
|
|
1896
|
+
createCommand
|
|
1897
|
+
]
|
|
1871
1898
|
});
|
|
1872
1899
|
}
|
|
1873
|
-
function extractHttpStatus(error) {
|
|
1874
|
-
if (!error || typeof error !== "object") return null;
|
|
1875
|
-
const candidate = error;
|
|
1876
|
-
if (typeof candidate.statusCode === "number") return candidate.statusCode;
|
|
1877
|
-
if (typeof candidate.status === "number") return candidate.status;
|
|
1878
|
-
if (typeof candidate.message === "string") {
|
|
1879
|
-
const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
|
|
1880
|
-
if (match) return Number.parseInt(match[1], 10);
|
|
1881
|
-
}
|
|
1882
|
-
return null;
|
|
1883
|
-
}
|
|
1884
1900
|
function noDeploymentsError(summary, why) {
|
|
1885
1901
|
return new CliError({
|
|
1886
1902
|
code: "NO_DEPLOYMENTS",
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
1
|
+
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
2
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
3
3
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
4
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
5
|
import { resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
6
|
+
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup } from "../lib/project/setup.js";
|
|
7
|
+
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
6
8
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
7
9
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
8
10
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
@@ -48,6 +50,44 @@ async function runProjectShow(context, explicitProject) {
|
|
|
48
50
|
nextSteps: []
|
|
49
51
|
};
|
|
50
52
|
}
|
|
53
|
+
async function runProjectCreate(context, projectName) {
|
|
54
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
55
|
+
if (!workspace) throw workspaceRequiredError();
|
|
56
|
+
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
57
|
+
if (!isRealMode(context)) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
|
|
58
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
59
|
+
if (!client) throw authRequiredError();
|
|
60
|
+
const provider = createPreviewAppProvider(client);
|
|
61
|
+
const name = projectName.trim();
|
|
62
|
+
const created = await provider.createProject({ name }).catch((error) => {
|
|
63
|
+
throw projectCreateFailedError(error, name, workspace, {
|
|
64
|
+
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
|
|
65
|
+
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
66
|
+
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
command: "project.create",
|
|
71
|
+
result: await bindProjectToDirectory(context, workspace, {
|
|
72
|
+
id: created.id,
|
|
73
|
+
name: created.name
|
|
74
|
+
}, "created"),
|
|
75
|
+
warnings: [],
|
|
76
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function runProjectLink(context, projectRef) {
|
|
80
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
81
|
+
if (!workspace) throw workspaceRequiredError();
|
|
82
|
+
if (!projectRef || !projectRef.trim()) throw usageError("Project link requires a Project id or name", "The command cannot choose a Project without an explicit id or name.", "Pass the Project id or name as the first argument.", ["prisma-cli project link proj_123"], "project");
|
|
83
|
+
const projects = isRealMode(context) ? await listRealProjectsForLink(context, workspace) : listFixtureWorkspaceProjects(context, workspace);
|
|
84
|
+
return {
|
|
85
|
+
command: "project.link",
|
|
86
|
+
result: await bindProjectToDirectory(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked"),
|
|
87
|
+
warnings: [],
|
|
88
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
51
91
|
async function runGitConnect(context, gitUrl, options = {}) {
|
|
52
92
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
53
93
|
if (!workspace) throw workspaceRequiredError();
|
|
@@ -163,6 +203,11 @@ async function resolveProjectShowInRealMode(context, workspace, explicitProject)
|
|
|
163
203
|
remember: false
|
|
164
204
|
});
|
|
165
205
|
}
|
|
206
|
+
async function listRealProjectsForLink(context, workspace) {
|
|
207
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
208
|
+
if (!client) throw authRequiredError();
|
|
209
|
+
return listRealWorkspaceProjects(client, workspace);
|
|
210
|
+
}
|
|
166
211
|
async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
|
|
167
212
|
return resolveProjectTarget({
|
|
168
213
|
context,
|
|
@@ -501,4 +546,4 @@ function toProjectSummary(project) {
|
|
|
501
546
|
};
|
|
502
547
|
}
|
|
503
548
|
//#endregion
|
|
504
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectList, runProjectShow };
|
|
549
|
+
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
@@ -198,4 +198,4 @@ function toProjectSummary(project) {
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
//#endregion
|
|
201
|
-
export { inferTargetName, projectNotFoundError, resolveProjectTarget, sortProjects };
|
|
201
|
+
export { inferTargetName, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { CliError, usageError } from "../../shell/errors.js";
|
|
2
|
+
import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
|
|
3
|
+
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
|
|
4
|
+
//#region src/lib/project/setup.ts
|
|
5
|
+
function isValidProjectSetupName(projectName) {
|
|
6
|
+
return projectName.trim().length > 0;
|
|
7
|
+
}
|
|
8
|
+
function resolveProjectForSetup(projectRef, projects, workspace) {
|
|
9
|
+
const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
|
|
10
|
+
if (matches.length === 1) return matches[0];
|
|
11
|
+
if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
|
|
12
|
+
throw projectNotFoundError(projectRef, workspace);
|
|
13
|
+
}
|
|
14
|
+
async function bindProjectToDirectory(context, workspace, project, action) {
|
|
15
|
+
await writeLocalResolutionPin(context.runtime.cwd, {
|
|
16
|
+
workspaceId: workspace.id,
|
|
17
|
+
projectId: project.id
|
|
18
|
+
});
|
|
19
|
+
await ensureLocalResolutionPinGitignore(context.runtime.cwd);
|
|
20
|
+
return {
|
|
21
|
+
workspace,
|
|
22
|
+
project,
|
|
23
|
+
directory: formatSetupDirectory(context.runtime.cwd),
|
|
24
|
+
localPin: {
|
|
25
|
+
path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
26
|
+
written: true
|
|
27
|
+
},
|
|
28
|
+
action
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function toProjectSummary(project) {
|
|
32
|
+
return {
|
|
33
|
+
id: project.id,
|
|
34
|
+
name: project.name
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function projectSetupNameRequiredError(command) {
|
|
38
|
+
return usageError("Project create requires a name", "The project name must be a non-empty value.", "Pass a Project name explicitly.", [`prisma-cli ${command} my-app`], "project");
|
|
39
|
+
}
|
|
40
|
+
function projectCreateFailedError(error, projectName, workspace, options) {
|
|
41
|
+
const status = extractHttpStatus(error);
|
|
42
|
+
if (status === 401 || status === 403) return new CliError({
|
|
43
|
+
code: "PROJECT_CREATE_FAILED",
|
|
44
|
+
domain: "project",
|
|
45
|
+
summary: `Could not create Project "${projectName}"`,
|
|
46
|
+
why: `The platform rejected the Project create in workspace "${workspace.name}" (HTTP ${status}).`,
|
|
47
|
+
fix: options.permissionFix,
|
|
48
|
+
debug: formatDebugDetails(error),
|
|
49
|
+
exitCode: 1,
|
|
50
|
+
nextSteps: options.nextSteps
|
|
51
|
+
});
|
|
52
|
+
return new CliError({
|
|
53
|
+
code: "PROJECT_CREATE_FAILED",
|
|
54
|
+
domain: "project",
|
|
55
|
+
summary: `Could not create Project "${projectName}"`,
|
|
56
|
+
why: error instanceof Error ? error.message : String(error),
|
|
57
|
+
fix: options.fallbackFix,
|
|
58
|
+
debug: formatDebugDetails(error),
|
|
59
|
+
exitCode: 1,
|
|
60
|
+
nextSteps: options.nextSteps
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function formatCommandArgument(value) {
|
|
64
|
+
return /^[A-Za-z0-9._/-]+$/.test(value) ? value : JSON.stringify(value);
|
|
65
|
+
}
|
|
66
|
+
function formatSetupDirectory(cwd) {
|
|
67
|
+
const basename = cwd.split(/[\\/]/).filter(Boolean).pop();
|
|
68
|
+
return basename ? `./${basename}` : ".";
|
|
69
|
+
}
|
|
70
|
+
function extractHttpStatus(error) {
|
|
71
|
+
if (!error || typeof error !== "object") return null;
|
|
72
|
+
const candidate = error;
|
|
73
|
+
if (typeof candidate.statusCode === "number") return candidate.statusCode;
|
|
74
|
+
if (typeof candidate.status === "number") return candidate.status;
|
|
75
|
+
if (typeof candidate.message === "string") {
|
|
76
|
+
const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
|
|
77
|
+
if (match) return Number.parseInt(match[1], 10);
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function formatDebugDetails(error) {
|
|
82
|
+
if (error instanceof Error) return error.stack ?? error.message;
|
|
83
|
+
return typeof error === "string" ? error : null;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { bindProjectToDirectory, formatCommandArgument, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderSummaryLine } from "../shell/ui.js";
|
|
1
2
|
import { renderList, renderMutate, renderShow, serializeList } from "../output/patterns.js";
|
|
2
3
|
//#region src/presenters/project.ts
|
|
3
4
|
function renderProjectList(context, descriptor, result) {
|
|
@@ -51,6 +52,14 @@ function renderProjectShow(context, descriptor, result) {
|
|
|
51
52
|
function serializeProjectShow(result) {
|
|
52
53
|
return result;
|
|
53
54
|
}
|
|
55
|
+
function renderProjectSetup(context, _descriptor, result) {
|
|
56
|
+
const lines = result.action === "created" ? [renderSummaryLine(context.ui, "success", `Created Project "${result.project.name}"`)] : [];
|
|
57
|
+
lines.push(renderSummaryLine(context.ui, "success", `Linked "${result.directory}" to Project "${result.project.name}"`), `Saved ${result.localPin.path}`);
|
|
58
|
+
return lines;
|
|
59
|
+
}
|
|
60
|
+
function serializeProjectSetup(result) {
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
54
63
|
function renderGitConnect(context, descriptor, result) {
|
|
55
64
|
const connection = result.repositoryConnection;
|
|
56
65
|
return renderMutate({
|
|
@@ -124,4 +133,4 @@ function formatGitConnectionDetail(status) {
|
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
//#endregion
|
|
127
|
-
export { renderGitConnect, renderGitDisconnect, renderProjectList, renderProjectShow, serializeProjectList, serializeProjectShow };
|
|
136
|
+
export { renderGitConnect, renderGitDisconnect, renderProjectList, renderProjectSetup, renderProjectShow, serializeProjectList, serializeProjectSetup, serializeProjectShow };
|
|
@@ -54,7 +54,11 @@ const DESCRIPTORS = [
|
|
|
54
54
|
id: "project",
|
|
55
55
|
path: ["prisma", "project"],
|
|
56
56
|
description: "Manage and inspect your Prisma projects",
|
|
57
|
-
examples: [
|
|
57
|
+
examples: [
|
|
58
|
+
"prisma-cli project list",
|
|
59
|
+
"prisma-cli project link proj_123",
|
|
60
|
+
"prisma-cli project create my-app"
|
|
61
|
+
]
|
|
58
62
|
},
|
|
59
63
|
{
|
|
60
64
|
id: "app",
|
|
@@ -94,6 +98,26 @@ const DESCRIPTORS = [
|
|
|
94
98
|
description: "Show which project is active for this directory",
|
|
95
99
|
examples: ["prisma-cli project show", "prisma-cli project show --project proj_123 --json"]
|
|
96
100
|
},
|
|
101
|
+
{
|
|
102
|
+
id: "project.create",
|
|
103
|
+
path: [
|
|
104
|
+
"prisma",
|
|
105
|
+
"project",
|
|
106
|
+
"create"
|
|
107
|
+
],
|
|
108
|
+
description: "Create a Project and link this directory",
|
|
109
|
+
examples: ["prisma-cli project create my-app", "prisma-cli project create my-app --json"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "project.link",
|
|
113
|
+
path: [
|
|
114
|
+
"prisma",
|
|
115
|
+
"project",
|
|
116
|
+
"link"
|
|
117
|
+
],
|
|
118
|
+
description: "Link this directory to an existing Project",
|
|
119
|
+
examples: ["prisma-cli project link proj_123", "prisma-cli project link \"Acme Dashboard\" --json"]
|
|
120
|
+
},
|
|
97
121
|
{
|
|
98
122
|
id: "git.connect",
|
|
99
123
|
path: [
|
|
@@ -183,10 +207,13 @@ const DESCRIPTORS = [
|
|
|
183
207
|
longDescription: "Agent skills for guided Next.js deploys are available from the Prisma CLI skill cluster.",
|
|
184
208
|
examples: [
|
|
185
209
|
"prisma-cli app deploy",
|
|
210
|
+
"prisma-cli app deploy --project proj_123",
|
|
211
|
+
"prisma-cli app deploy --create-project my-app --yes",
|
|
186
212
|
"prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
|
|
187
213
|
"prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
|
|
188
214
|
"prisma-cli app deploy --branch feat-login --framework hono",
|
|
189
|
-
"pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all"
|
|
215
|
+
"pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all",
|
|
216
|
+
"prisma-cli app deploy --framework bun --entry src/server.ts"
|
|
190
217
|
]
|
|
191
218
|
},
|
|
192
219
|
{
|