Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ yarn dlx create-prisma@latest my-app
bunx create-prisma@latest my-app
```

The CLI initializes Prisma 8 with `prisma@next`, installs dependencies, emits the contract, and generates a deployable Composer app. PostgreSQL projects use Composer's native Prisma Postgres provider, including migrations and a typed runtime client.
The CLI initializes Prisma 8 with the compatible consolidated Prisma CLI, installs dependencies, emits the contract, and generates a deployable Composer app. PostgreSQL projects use Composer's native Prisma Postgres provider, including migrations and a typed runtime client.

The deployment prompt is:

Expand All @@ -34,6 +34,7 @@ workspace. Choosing another workspace also updates the Prisma CLI's active works
- `elysia`
- `nest`
- `next`
- `turborepo` (Next.js monorepo with a shared Prisma database package)
- `svelte` (SvelteKit)
- `astro`
- `nuxt`
Expand Down
5 changes: 5 additions & 0 deletions src/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ async function promptForCreateTemplate(output: Writable): Promise<CreateTemplate
label: "Next.js",
hint: "Full-stack React app with App Router",
},
{
value: "turborepo",
label: "Monorepo (Turborepo)",
hint: "Next.js app with a shared Prisma database package",
},
{
value: "svelte",
label: "SvelteKit",
Expand Down
15 changes: 14 additions & 1 deletion src/constants/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const dependencyVersionMap = {
"@elysiajs/node": "^1.4.5",
"@prisma/composer": "0.16.0",
"@prisma/composer-prisma-cloud": "0.16.0",
"@prisma/dev": "0.24.7",
"@prisma/orm-mongo": "8.0.0-rc.8",
// Must match @prisma/composer-prisma-cloud's exact peerDependency.
"@prisma/orm-postgres": "8.0.0-rc.8",
Expand All @@ -22,6 +23,7 @@ export const dependencyVersionMap = {
// The ORM runtime's timestamp columns need a global Temporal, which no
// stable Node or Bun ships yet.
"temporal-polyfill": "^1.0.4",
turbo: "2.10.12",
tsx: "^4.21.0",
typescript: "^5.9.3",
} as const;
Expand Down Expand Up @@ -87,12 +89,23 @@ export function getCreateTemplateDependencies(
if (template === "tanstack-start") {
devDependencies.push("nitro");
}
if (template === "turborepo") {
devDependencies.push("turbo");
}

return [
const targets: CreateTemplateDependencyTarget[] = [
{
packageJsonPath: "package.json",
dependencies,
devDependencies,
},
];
if (template === "turborepo") {
targets.push({
packageJsonPath: "packages/database/package.json",
dependencies: [],
devDependencies: ["typescript"],
});
}
return targets;
}
11 changes: 11 additions & 0 deletions src/tasks/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,31 @@ export async function writePrismaDependencies(
packageManager: PackageManager,
_authoring: AuthoringStyle,
projectDir = process.cwd(),
template?: CreateTemplate,
): Promise<void> {
const dependencies = [getDbPackages(provider)];
if (provider === "postgres" && packageManager !== "deno") dependencies.push("temporal-polyfill");
if (provider === "mongo") dependencies.push("arktype", "mongodb");
if (packageManager === "deno") dependencies.push("dotenv");

const devDependencies = ["@types/node", "prisma"];
if (packageManager !== "deno") devDependencies.push("@prisma/dev");

await addPackageDependency({
dependencies,
devDependencies,
scripts: getPrismaScriptMap(packageManager),
projectDir,
});

if (template === "turborepo" && packageManager !== "deno") {
const databaseDependencies = [getDbPackages(provider)];
if (provider === "postgres") databaseDependencies.push("temporal-polyfill");
await addPackageDependency({
dependencies: databaseDependencies,
projectDir: path.join(projectDir, "packages/database"),
});
}
}

export async function writeCreateTemplateDependencies(opts: {
Expand Down
22 changes: 16 additions & 6 deletions src/tasks/setup-prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
type CreateFailureStage,
} from "../create-outcome";
import type { CreateNextStep } from "../result";
import { scaffoldCreateSharedTemplates } from "../templates/render-create-template";
import {
getCreatePrismaSourceDir,
scaffoldCreateSharedTemplates,
} from "../templates/render-create-template";
import {
AuthoringStyleSchema,
DatabaseProviderSchema,
Expand Down Expand Up @@ -221,8 +224,10 @@ export async function collectPrismaSetupContext(
};
}

function getContractPath(authoring: AuthoringStyle) {
return `src/prisma/contract${authoring === "typescript" ? ".ts" : ".prisma"}`;
function getContractPath(authoring: AuthoringStyle, template: CreateTemplate) {
return `${getCreatePrismaSourceDir(template)}/contract${
authoring === "typescript" ? ".ts" : ".prisma"
}`;
}

function getInitTarget(provider: DatabaseProvider): "postgres" | "mongodb" {
Expand All @@ -235,7 +240,11 @@ function getPrismaCliInvocation(packageManager: PackageManager, args: string[])
return getPackageExecutionArgs(packageManager, [packageName, ...args]);
}

async function runPrismaInit(context: PrismaSetupContext, projectDir: string): Promise<void> {
async function runPrismaInit(
context: PrismaSetupContext,
projectDir: string,
template: CreateTemplate,
): Promise<void> {
const args = [
"orm",
"init",
Expand All @@ -246,7 +255,7 @@ async function runPrismaInit(context: PrismaSetupContext, projectDir: string): P
"--authoring",
context.authoring,
"--schema-path",
getContractPath(context.authoring),
getContractPath(context.authoring, template),
"--skip-install",
];
const invocation = getPrismaCliInvocation(context.packageManager, args);
Expand Down Expand Up @@ -460,7 +469,7 @@ export async function executePrismaSetupContext(

try {
progress?.message("Preparing Prisma 8 project files...");
await runPrismaInit(context, projectDir);
await runPrismaInit(context, projectDir, template);

setupStage = "configure_project";
setupReason = "project_configuration_failed";
Expand All @@ -477,6 +486,7 @@ export async function executePrismaSetupContext(
context.packageManager,
context.authoring,
projectDir,
template,
);
await ensureComposerTypeScriptOptions(projectDir);
if (context.databaseProvider === "mongo") await ensureMongoEnvironment(projectDir);
Expand Down
28 changes: 28 additions & 0 deletions src/templates/render-create-template.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import path from "node:path";

import type { AuthoringStyle, CreateTemplate, DatabaseProvider, PackageManager } from "../types";
import { renderTemplateTree, resolveTemplatesDir } from "./shared";

const DEFAULT_PRISMA_SOURCE_DIR = "src/prisma";
const TURBOREPO_PRISMA_SOURCE_DIR = "packages/database/src";

type CreateTemplateContext = {
projectName: string;
template: CreateTemplate;
Expand All @@ -17,6 +22,10 @@ function getCreateSharedTemplateDir(): string {
return resolveTemplatesDir("templates/create/_shared");
}

export function getCreatePrismaSourceDir(template: CreateTemplate): string {
return template === "turborepo" ? TURBOREPO_PRISMA_SOURCE_DIR : DEFAULT_PRISMA_SOURCE_DIR;
}

function createTemplateContext(
projectName: string,
template: CreateTemplate,
Expand Down Expand Up @@ -46,6 +55,18 @@ export async function scaffoldCreateSharedTemplates(opts: {
templateRoot: getCreateSharedTemplateDir(),
outputDir: projectDir,
context: createTemplateContext(projectName, template, provider, authoring, packageManager),
mapRelativeOutputPath(relativePath) {
if (template !== "turborepo") return relativePath;
const relativePrismaPath = path.relative(DEFAULT_PRISMA_SOURCE_DIR, relativePath);
if (
relativePrismaPath === "" ||
relativePrismaPath === ".." ||
relativePrismaPath.startsWith(`..${path.sep}`)
) {
return relativePath;
}
return path.join(TURBOREPO_PRISMA_SOURCE_DIR, relativePrismaPath);
},
});
}

Expand Down Expand Up @@ -77,4 +98,11 @@ export async function scaffoldCreateFrameworkTemplate(opts: {
outputDir: projectDir,
context,
});
if (template === "turborepo") {
await renderTemplateTree<CreateTemplateContext>({
templateRoot: getCreateTemplateDir("next"),
outputDir: path.join(projectDir, "apps/web"),
context,
});
}
}
9 changes: 7 additions & 2 deletions src/templates/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "../utils/package-manager";

Handlebars.registerHelper("eq", (left: unknown, right: unknown) => left === right);
Handlebars.registerHelper("or", (...args: unknown[]) => args.slice(0, -1).some(Boolean));
Handlebars.registerHelper(
"runScriptCommand",
(packageManager: PackageManager | undefined, scriptName: string) =>
Expand Down Expand Up @@ -126,13 +127,17 @@ export async function renderTemplateTree<TContext>(opts: {
templateRoot: string;
outputDir: string;
context: TContext;
mapRelativeOutputPath?: (relativePath: string) => string;
}): Promise<void> {
const { templateRoot, outputDir, context } = opts;
const { templateRoot, outputDir, context, mapRelativeOutputPath } = opts;
const templateFiles = await getTemplateFilesRecursively(templateRoot);

for (const templateFilePath of templateFiles) {
const relativeTemplatePath = path.relative(templateRoot, templateFilePath);
const relativeOutputPath = stripHbsExtension(relativeTemplatePath);
const renderedRelativePath = stripHbsExtension(relativeTemplatePath);
const relativeOutputPath = mapRelativeOutputPath
? mapRelativeOutputPath(renderedRelativePath)
: renderedRelativePath;
const outputPath = path.join(outputDir, relativeOutputPath);
await renderTemplateFile({
templateFilePath,
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const createTemplates = [
"elysia",
"nest",
"next",
"turborepo",
"svelte",
"astro",
"nuxt",
Expand Down
12 changes: 6 additions & 6 deletions templates/create/_shared/.gitattributes.hbs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{{#if (eq authoring "typescript")}}
src/prisma/generated/contract.json linguist-generated
src/prisma/generated/contract.d.ts linguist-generated
{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/generated/contract.json linguist-generated
{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/generated/contract.d.ts linguist-generated
{{else}}
src/prisma/contract.json linguist-generated
src/prisma/contract.d.ts linguist-generated
{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/contract.json linguist-generated
{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/contract.d.ts linguist-generated
{{/if}}
src/prisma/ops.json linguist-generated
src/prisma/migration.json linguist-generated
{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/ops.json linguist-generated
{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/migration.json linguist-generated
migrations/snapshots/**/contract.json linguist-generated
migrations/snapshots/**/contract.d.ts linguist-generated
44 changes: 44 additions & 0 deletions templates/create/_shared/README.md.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,50 @@ deno task contract:emit
```

Prisma Compute does not support Deno deployments yet.
{{else if (eq template "turborepo")}}
A Prisma 8 monorepo powered by Turborepo, Next.js, and Prisma Composer.

## Workspace layout

- `apps/web` — Next.js application
- `packages/database` — Prisma contract, generated artifacts, runtime client, and seed data
- `module.ts` and `service.ts` — Composer deployment topology

## Run locally

```bash
{{runScriptCommand packageManager "dev:composer"}}
```

This builds the workspace and starts it with Composer. PostgreSQL projects get a local Prisma Postgres database and apply the committed migrations automatically.

## Deploy

```bash
{{runScriptCommand packageManager "deploy"}}
```

The deploy script runs the Turborepo build, provisions Prisma Postgres when selected, applies migrations, and deploys the Next.js app to Prisma Compute.

The starter users are inserted idempotently from `packages/database/src/seed.ts` on the first database query through the Composer service binding.

{{#if (eq provider "mongo")}}
MongoDB is not provisioned by Composer. Set `MONGODB_URL` before running Composer locally or deploying.
{{/if}}

## Prisma

- Contract: `packages/database/src/contract{{#if (eq authoring "typescript")}}.ts{{else}}.prisma{{/if}}`
- Prisma and Composer config: `prisma.config.ts`
- Composer app: `module.ts` and `service.ts`

After changing the contract, run:

```bash
{{runScriptCommand packageManager "contract:emit"}}
```

To run the workspace's development tasks directly, use `{{runScriptCommand packageManager "dev"}}`. This direct mode requires `{{#if (eq provider "postgres")}}DATABASE_URL{{else}}MONGODB_URL{{/if}}`.
{{else}}
A minimal {{template}} app with Prisma 8 and Prisma Composer.

Expand Down
2 changes: 1 addition & 1 deletion templates/create/_shared/module.ts.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { module } from "@prisma/composer";
{{#if (eq provider "postgres")}}
import { postgres } from "@prisma/composer-prisma-cloud/orm";

import { appContract } from "./src/prisma/composer.ts";
import { appContract } from "./{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/composer.ts";
{{else}}
import { envSecret } from "@prisma/composer-prisma-cloud";
{{/if}}
Expand Down
7 changes: 6 additions & 1 deletion templates/create/_shared/pnpm-workspace.yaml.hbs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{{#if (eq packageManager "pnpm")}}
{{#if (eq template "turborepo")}}
packages:
- "apps/*"
- "packages/*"
{{/if}}
allowBuilds:
esbuild: true
msgpackr-extract: true
{{#if (eq template "next")}}
{{#if (or (eq template "next") (eq template "turborepo"))}}
sharp: true
unrs-resolver: true
{{else if (eq template "astro")}}
Expand Down
4 changes: 2 additions & 2 deletions templates/create/_shared/prisma-composer.config.ts.hbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{{#unless (eq packageManager "deno")}}
import { defineConfig } from "@prisma/composer/config";
import { nodeBuild } from "@prisma/composer/node/control";
{{#if (eq template "next")}}
{{#if (or (eq template "next") (eq template "turborepo"))}}
import { nextjsBuild } from "@prisma/composer/nextjs/control";
{{/if}}
import { prismaCloud, prismaState } from "@prisma/composer-prisma-cloud/control";

export default defineConfig({
extensions: [prismaCloud(), nodeBuild(){{#if (eq template "next")}}, nextjsBuild(){{/if}}],
extensions: [prismaCloud(), nodeBuild(){{#if (or (eq template "next") (eq template "turborepo"))}}, nextjsBuild(){{/if}}],
state: prismaState(),
});
{{/unless}}
8 changes: 4 additions & 4 deletions templates/create/_shared/prisma.config.ts.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { defineConfig as ormConfig } from "@prisma/orm-{{#if (eq provider "postg

export default definePrismaConfig({
orm: ormConfig({
contract: "./src/prisma/contract{{#if (eq authoring "typescript")}}.ts{{else}}.prisma{{/if}}",
contract: "./{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/contract{{#if (eq authoring "typescript")}}.ts{{else}}.prisma{{/if}}",
{{#if (eq authoring "typescript")}}
output: "./src/prisma/generated",
output: "./{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/generated",
{{/if}}
db: {
connection: process.env.{{#if (eq provider "postgres")}}DATABASE_URL{{else}}MONGODB_URL{{/if}}!,
Expand All @@ -23,9 +23,9 @@ export default definePrismaConfig({
agents: ["claude", "cursor", "agents", "devin"],
},
orm: ormConfig({
contract: "./src/prisma/contract{{#if (eq authoring "typescript")}}.ts{{else}}.prisma{{/if}}",
contract: "./{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/contract{{#if (eq authoring "typescript")}}.ts{{else}}.prisma{{/if}}",
{{#if (eq authoring "typescript")}}
output: "./src/prisma/generated",
output: "./{{#if (eq template "turborepo")}}packages/database/src{{else}}src/prisma{{/if}}/generated",
{{/if}}
db: {
connection: process.env.{{#if (eq provider "postgres")}}DATABASE_URL{{else}}MONGODB_URL{{/if}}!,
Expand Down
Loading
Loading