@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.
@@ -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
- ])).addOption(new Option("--entry <path>", "Entrypoint path for Bun or auto deploys")).addOption(new Option("--build-type <type>", "Legacy deploy build type").choices([...PREVIEW_BUILD_TYPES]).default("auto").hideHelp()).addOption(new Option("--http-port <port>", "HTTP port override for the deployed app")).addOption(new Option("--env <name=value>", "Environment variable").argParser(collectRepeatableValues));
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);
@@ -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, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
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
- const skipLocalPin = Boolean(envProjectId || envAppId);
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
- let framework = await resolveDeployFramework(context, {
112
+ assertSupportedEntrypointForRequestedDeployShape({
106
113
  requestedFramework: options?.framework,
107
- requestedBuildType: options?.buildType,
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
- firstDeploy: selectedApp.firstDeploy,
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
- branchAnnotation: branch.annotation,
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
- explicitBuildType,
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: options?.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: shouldWriteLocalPin ? {
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 || envAppId);
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, provider, explicitProject, options);
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, provider, explicitProject, options) {
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
- const createProject = options.allowCreate ? async (name) => {
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
- listProjects: async () => projects,
1338
- createProject,
1339
- allowCreate: options.allowCreate,
1340
- prompt: createSelectPromptPort(context),
1341
- remember: true
1342
- }), branch);
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
- return withDeployBranch(await resolveProjectTarget({
1372
- context,
1352
+ const platformMapping = await resolveDurablePlatformMapping();
1353
+ if (platformMapping && platformMapping.workspace.id === workspace.id) return withDeployBranch({
1373
1354
  workspace,
1374
- listProjects: async () => projects,
1375
- createProject,
1376
- allowCreate: options.allowCreate,
1377
- prompt: createSelectPromptPort(context),
1378
- remember: true
1379
- }), branch);
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.explicitBuildType) {
1440
- const buildType = normalizeBuildType(options.requestedBuildType);
1441
- if (buildType !== "auto") return {
1442
- key: buildType,
1443
- buildType,
1444
- displayName: formatBuildTypeName(buildType),
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 (hasPackageDependency(packageJson, "@tanstack/start")) return {
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": return {
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, or pass --framework <nextjs|hono|tanstack-start>.",
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
- if (!details.firstDeploy) {
1562
- context.output.stderr.write(`Deploying ${directory} to ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
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 maybeRenderLocalPinBound(context, projectName) {
1660
+ function maybeRenderProjectLinked(context, directory, projectName, localPinPath) {
1606
1661
  if (context.flags.json || context.flags.quiet) return;
1607
- context.output.stderr.write(`This directory is now linked to project ${projectName}.\n\n`);
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.explicitBuildType || options.explicitHttpPort || !canPrompt(context)) return {
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) 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");
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
- * `app deploy` falls into "create a new project on first deploy" when no
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: "DEPLOY_FAILED",
1864
- domain: "app",
1865
- summary: "Could not create a new project for this deploy",
1866
- why: `${inferredContext} ${errorMessage}`.trim(),
1867
- fix: "Pass --project <id-or-name> to deploy into an existing project, or retry after addressing the platform error above.",
1868
- debug: formatDebugDetails(error),
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: ["prisma-cli project list", "prisma-cli project show"]
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
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "3.0.0-dev.38.1",
3
+ "version": "3.0.0-dev.40.1",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {