Skip to content

Commit fa13c41

Browse files
authored
Merge pull request #34 from prisma/feat/compute-deploy-integration
feat: deploy to Prisma Compute from create-prisma
2 parents ea03c9b + e83ed19 commit fa13c41

27 files changed

Lines changed: 890 additions & 72 deletions

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Scaffold a new app with Prisma already wired up.
1313
- adds `db:generate`, `db:migrate`, and `db:seed` scripts
1414
- creates or updates `.env` with `DATABASE_URL`
1515
- can install dependencies and run `prisma generate` for you
16+
- can deploy the finished app to Prisma Compute and return a live URL
1617

1718
## Quick Start
1819

@@ -76,6 +77,14 @@ Use Prisma Postgres auto-provisioning:
7677
create-prisma --name my-app --template nest --provider postgresql --prisma-postgres
7778
```
7879

80+
Deploy a supported app to Prisma Compute:
81+
82+
```bash
83+
create-prisma --name my-api --template hono --provider postgresql --deploy
84+
```
85+
86+
With PostgreSQL, no `--database-url`, and no `--no-prisma-postgres`, the Compute flow creates a Prisma Compute project, creates a `main` Prisma Postgres database on the `main` branch, writes `DATABASE_URL` to `.env`, and deploys the app with environment variables loaded from `.env`.
87+
7988
## Supported Templates
8089

8190
- `hono`
@@ -88,6 +97,13 @@ create-prisma --name my-app --template nest --provider postgresql --prisma-postg
8897
- `tanstack-start`
8998
- `turborepo`
9099

100+
Prisma Compute deployment is currently supported for:
101+
102+
- `hono`
103+
- `elysia`
104+
- `next`
105+
- `tanstack-start`
106+
91107
## Supported Databases
92108

93109
- `postgresql`
@@ -111,6 +127,7 @@ create-prisma --name my-app --template nest --provider postgresql --prisma-postg
111127
- `--provider` choose the database provider
112128
- `--package-manager` choose the package manager/runtime
113129
- `--schema-preset empty|basic`
130+
- `--deploy` deploy supported templates to Prisma Compute
114131
- `--yes` accept defaults and skip prompts
115132
- `--no-install` scaffold only
116133
- `--no-generate` skip `prisma generate`
@@ -129,6 +146,22 @@ create-prisma --name my-app --template nest --provider postgresql --prisma-postg
129146

130147
These can be selected interactively or enabled with flags.
131148

149+
## Deploy to Prisma Compute
150+
151+
After scaffolding, `create-prisma` can deploy your app to [Prisma Compute](https://www.prisma.io/docs/compute), the serverless hosting for TypeScript apps that runs next to your Prisma Postgres database. It is offered for the templates the Prisma CLI can deploy today: `hono`, `elysia`, `next`, and `tanstack-start`.
152+
153+
Accept the prompt ("Deploy to Prisma Compute now?") when it appears, or pass the flag:
154+
155+
```bash
156+
create-prisma --name my-api --template hono --provider postgresql --deploy
157+
```
158+
159+
The deploy step signs you in with the Prisma CLI if you are not signed in yet. With PostgreSQL, no `--database-url`, and no `--no-prisma-postgres`, setup creates a Prisma Compute project, creates a `main` Prisma Postgres database on the `main` branch, writes `DATABASE_URL` to `.env`, runs the requested Prisma setup, then deploys the app with environment variables loaded from `.env`.
160+
161+
A `compute:deploy` script is added to the generated project so you can redeploy app changes later. That script only runs `@prisma/cli@latest app deploy` with `.env`; it does not create a new project, create a new database, run migrations, or seed data.
162+
163+
The deploy prompt is skipped in `--yes` runs unless you pass `--deploy`. Browser sign-in may still need a person at the keyboard if no Prisma CLI session exists.
164+
132165
## Local Development
133166

134167
```bash

src/commands/create.ts

Lines changed: 141 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { cancel, intro, isCancel, log, select, spinner, text } from "@clack/prompts";
1+
import { cancel, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
22
import fs from "fs-extra";
33
import path from "node:path";
44

55
import { scaffoldCreateTemplate } from "../templates/render-create-template";
6-
import { writeCreateTemplateDependencies } from "../tasks/install";
6+
import { addPackageDependency, writeCreateTemplateDependencies } from "../tasks/install";
77
import type { CreateAddonSetupContext } from "../tasks/setup-addons";
88
import type { PrismaSetupContext } from "../tasks/setup-prisma";
99
import {
@@ -13,17 +13,31 @@ import {
1313
type CreateTemplate,
1414
type SchemaPreset,
1515
} from "../types";
16-
import { collectPrismaSetupContext, executePrismaSetupContext } from "../tasks/setup-prisma";
16+
import {
17+
collectPrismaSetupInitialContext,
18+
completePrismaSetupContext,
19+
executePrismaSetupContext,
20+
} from "../tasks/setup-prisma";
1721
import {
1822
collectCreateAddonSetupContext,
1923
executeCreateAddonSetupContext,
2024
} from "../tasks/setup-addons";
25+
import {
26+
collectComputeDeployContext,
27+
executeComputeDatabaseSetup,
28+
executeComputeDeployContext,
29+
getComputeDeployScriptMap,
30+
type ComputeDatabaseResult,
31+
type ComputeDeployContext,
32+
type ComputeDeployResult,
33+
} from "../tasks/deploy-to-compute";
2134
import {
2235
trackCreateCompleted,
2336
trackCreateFailed,
2437
type CreateTelemetryFailureStage,
2538
} from "../telemetry";
2639
import { getCreatePrismaIntro } from "../ui/branding";
40+
import { getRunScriptCommand } from "../utils/package-manager";
2741

2842
const DEFAULT_PROJECT_NAME = "my-app";
2943
const DEFAULT_TEMPLATE: CreateTemplate = "hono";
@@ -43,6 +57,8 @@ export type CreatePromptContext = {
4357
projectPackageName: string;
4458
prismaSetupContext: PrismaSetupContext;
4559
addonSetupContext?: CreateAddonSetupContext;
60+
computeDeployContext?: ComputeDeployContext;
61+
useComputeDatabase: boolean;
4662
};
4763

4864
type ExecuteCreateContextResult =
@@ -288,10 +304,36 @@ async function collectCreateContext(
288304
return;
289305
}
290306

291-
const prismaSetupContext = await collectPrismaSetupContext(input, {
307+
const prismaSetupInitialContext = await collectPrismaSetupInitialContext(input, {
292308
projectDir: targetDirectory,
293309
defaultSchemaPreset: DEFAULT_SCHEMA_PRESET,
294310
});
311+
if (!prismaSetupInitialContext) {
312+
return;
313+
}
314+
315+
const projectPackageName = toPackageName(path.basename(targetDirectory));
316+
317+
const computeDeployContext = await collectComputeDeployContext(input, {
318+
template,
319+
packageManager: prismaSetupInitialContext.packageManager,
320+
useDefaults,
321+
defaultServiceName: projectPackageName,
322+
});
323+
if (computeDeployContext === undefined) {
324+
return;
325+
}
326+
327+
const useComputeDatabase = Boolean(
328+
computeDeployContext &&
329+
prismaSetupInitialContext.databaseProvider === "postgresql" &&
330+
!prismaSetupInitialContext.databaseUrl &&
331+
input.prismaPostgres !== false,
332+
);
333+
334+
const prismaSetupContext = await completePrismaSetupContext(input, prismaSetupInitialContext, {
335+
useComputePostgres: useComputeDatabase,
336+
});
295337
if (!prismaSetupContext) {
296338
return;
297339
}
@@ -310,9 +352,11 @@ async function collectCreateContext(
310352
targetPathState,
311353
force,
312354
template,
313-
projectPackageName: toPackageName(path.basename(targetDirectory)),
355+
projectPackageName,
314356
prismaSetupContext,
315357
addonSetupContext: addonSetupContext ?? undefined,
358+
computeDeployContext: computeDeployContext ?? undefined,
359+
useComputeDatabase,
316360
};
317361
}
318362

@@ -329,6 +373,7 @@ async function executeCreateContext(
329373
schemaPreset: context.prismaSetupContext.schemaPreset,
330374
provider: context.prismaSetupContext.databaseProvider,
331375
packageManager: context.prismaSetupContext.packageManager,
376+
compute: Boolean(context.computeDeployContext),
332377
});
333378
scaffoldSpinner.stop("Project files scaffolded.");
334379
} catch (error) {
@@ -346,6 +391,13 @@ async function executeCreateContext(
346391
packageManager: context.prismaSetupContext.packageManager,
347392
projectDir: context.targetDirectory,
348393
});
394+
if (context.computeDeployContext) {
395+
await addPackageDependency({
396+
scripts: getComputeDeployScriptMap(context.computeDeployContext),
397+
scriptMode: "if-missing",
398+
projectDir: context.targetDirectory,
399+
});
400+
}
349401
} catch (error) {
350402
return {
351403
ok: false,
@@ -385,14 +437,49 @@ async function executeCreateContext(
385437
}
386438
}
387439

440+
let computeDatabaseResult: ComputeDatabaseResult | undefined;
441+
if (context.useComputeDatabase && context.computeDeployContext) {
442+
try {
443+
const result = await executeComputeDatabaseSetup({
444+
context: context.computeDeployContext,
445+
projectDir: context.targetDirectory,
446+
});
447+
if (!result.ok && !result.cancelled) {
448+
return {
449+
ok: false,
450+
stage: "compute_deploy",
451+
error: result.error,
452+
};
453+
}
454+
if (result.ok) {
455+
computeDatabaseResult = result.result;
456+
}
457+
} catch (error) {
458+
return {
459+
ok: false,
460+
stage: "compute_deploy",
461+
error,
462+
};
463+
}
464+
}
465+
466+
const prismaSetupContext = computeDatabaseResult
467+
? {
468+
...context.prismaSetupContext,
469+
databaseUrl: computeDatabaseResult.databaseUrl,
470+
shouldUsePrismaPostgres: false,
471+
}
472+
: context.prismaSetupContext;
473+
474+
let prismaResult: Awaited<ReturnType<typeof executePrismaSetupContext>>;
388475
try {
389-
const didSetupPrisma = await executePrismaSetupContext(context.prismaSetupContext, {
476+
prismaResult = await executePrismaSetupContext(prismaSetupContext, {
390477
prependNextSteps: nextSteps,
391478
projectDir: context.targetDirectory,
392-
includeDevNextStep: true,
479+
includeDevNextStep: !context.useComputeDatabase,
393480
});
394481

395-
if (!didSetupPrisma) {
482+
if (!prismaResult.ok) {
396483
return {
397484
ok: false,
398485
stage: "prisma_setup",
@@ -406,5 +493,51 @@ async function executeCreateContext(
406493
};
407494
}
408495

496+
let deployResult: ComputeDeployResult | undefined;
497+
if (context.computeDeployContext) {
498+
try {
499+
const result = await executeComputeDeployContext({
500+
context: context.computeDeployContext,
501+
projectDir: context.targetDirectory,
502+
createProject: !computeDatabaseResult,
503+
});
504+
if (!result.ok && !result.cancelled) {
505+
return {
506+
ok: false,
507+
stage: "compute_deploy",
508+
error: result.error,
509+
};
510+
}
511+
if (result.ok) {
512+
deployResult = result.result;
513+
prismaResult.nextSteps.push(
514+
`- ${getRunScriptCommand(context.prismaSetupContext.packageManager, "compute:deploy")}`,
515+
);
516+
}
517+
} catch (error) {
518+
return {
519+
ok: false,
520+
stage: "compute_deploy",
521+
error,
522+
};
523+
}
524+
}
525+
526+
const summaryLines: string[] = [];
527+
summaryLines.push(`Setup complete.${prismaResult.warningSection}`);
528+
if (deployResult) {
529+
const database = computeDatabaseResult?.database ?? deployResult.database;
530+
summaryLines.push(
531+
"",
532+
"Deployed to Prisma Compute:",
533+
...(deployResult.appUrl ? [`- App URL: ${deployResult.appUrl}`] : []),
534+
`- App: ${deployResult.appName} (${deployResult.appId})`,
535+
`- Deployment: ${deployResult.deploymentId}`,
536+
...(database ? [`- Database: ${database.name} (${database.id})`] : []),
537+
);
538+
}
539+
summaryLines.push("", "Next steps:", prismaResult.nextSteps.join("\n"));
540+
outro(summaryLines.join("\n"));
541+
409542
return { ok: true };
410543
}

0 commit comments

Comments
 (0)