Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 13 additions & 1 deletion src/constants/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 +88,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;
}
19 changes: 17 additions & 2 deletions src/tasks/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,12 @@ 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 === "postgres" && packageManager !== "deno" && template !== "turborepo") {
dependencies.push("temporal-polyfill");
}
if (provider === "mongo") dependencies.push("arktype", "mongodb");
if (packageManager === "deno") dependencies.push("dotenv");

Expand All @@ -156,6 +159,15 @@ export async function writePrismaDependencies(
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 All @@ -174,7 +186,10 @@ export async function writeCreateTemplateDependencies(opts: {
dependencies: target.dependencies,
devDependencies: target.devDependencies,
customDependencies: target.customDependencies,
scripts: getComposerScriptMap(packageManager),
scripts:
target.packageJsonPath === "package.json"
? getComposerScriptMap(packageManager)
: undefined,
projectDir: path.join(projectDir, path.dirname(target.packageJsonPath)),
});
}
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