Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ workspace. Choosing another workspace also updates the Prisma CLI's active works
- `elysia`
- `nest`
- `next`
- `turborepo` (Hello World Node.js server in `apps/server`, shared Prisma package in `packages/database`)
- `svelte` (SvelteKit)
- `astro`
- `nuxt`
Expand Down
5 changes: 5 additions & 0 deletions src/commands/create-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ const promptForCreateTemplate = Effect.fn("Prompts.template")(function* (output:
hint: "Structured Node API with controllers and services",
},
{ value: "next", label: "Next.js", hint: "Full-stack React app with App Router" },
{
value: "turborepo",
label: "Monorepo (Turborepo)",
hint: "Hello World server with a shared Prisma database package",
},
{ value: "svelte", label: "SvelteKit", hint: "Full-stack Svelte 5 app with Vite" },
{ value: "astro", label: "Astro", hint: "Content-oriented web app with server routes" },
{ value: "nuxt", label: "Nuxt", hint: "Full-stack Vue app with Nitro server routes" },
Expand Down
22 changes: 20 additions & 2 deletions src/constants/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const dependencyVersionMap = {
// stable Node or Bun ships yet.
"temporal-polyfill": "^1.0.4",
tsdown: "^0.22.14",
turbo: "2.10.12",
tsx: "^4.21.0",
typescript: "^5.9.3",
} as const;
Expand Down Expand Up @@ -54,7 +55,7 @@ function usesTsdown(template: CreateTemplate): boolean {

export function getCreateTemplateDependencies(
template: CreateTemplate,
_packageManager: PackageManager,
packageManager: PackageManager,
): CreateTemplateDependencyTarget[] {
const dependencies = ["@prisma/composer", "@prisma/composer-prisma-cloud", "alchemy"];
const devDependencies: string[] = [];
Expand All @@ -79,12 +80,29 @@ export function getCreateTemplateDependencies(
if (template === "tanstack-start") {
devDependencies.push("nitro");
}
if (template === "turborepo") devDependencies.push("turbo", "typescript");

return [
const targets: CreateTemplateDependencyTarget[] = [
{
packageJsonPath: "package.json",
dependencies,
devDependencies,
},
];
if (template === "turborepo") {
targets.push({
packageJsonPath: "apps/server/package.json",
dependencies: [],
devDependencies: ["@types/node", "tsdown", "tsx", "typescript"],
customDependencies: {
"@repo/database": packageManager === "npm" ? "*" : "workspace:*",
},
});
targets.push({
packageJsonPath: "packages/database/package.json",
dependencies: [],
devDependencies: ["typescript"],
});
}
return targets;
}
25 changes: 20 additions & 5 deletions src/tasks/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,30 @@ export const writePrismaDependenciesEffect = Effect.fn("Dependencies.writePrisma
packageManager: PackageManager,
_authoring: AuthoringStyle,
projectDir = process.cwd(),
options: { skillsSync?: boolean } = {},
options: { skillsSync?: boolean; template?: CreateTemplate } = {},
) {
const dependencies = [getDbPackages(provider)];
if (provider === "postgres" && packageManager !== "deno") dependencies.push("temporal-polyfill");
if (provider === "mongo") dependencies.push("arktype", "mongodb");
const databaseDependencies = [getDbPackages(provider)];
if (provider === "postgres" && packageManager !== "deno") {
databaseDependencies.push("temporal-polyfill");
}
if (provider === "mongo") databaseDependencies.push("arktype", "mongodb");
const dependencies =
options.template === "turborepo"
? [getDbPackages(provider), ...(provider === "mongo" ? ["arktype"] : [])]
: [...databaseDependencies];
if (packageManager === "deno") dependencies.push("dotenv");
yield* addPackageDependencyEffect({
dependencies,
devDependencies: ["@types/node", "prisma"],
scripts: getPrismaScriptMap(packageManager, options.skillsSync ?? true),
projectDir,
});
if (options.template === "turborepo") {
yield* addPackageDependencyEffect({
dependencies: databaseDependencies,
projectDir: path.join(projectDir, "packages/database"),
});
}
});

export const writeCreateTemplateDependenciesEffect = Effect.fn("Dependencies.writeTemplate")(
Expand All @@ -189,7 +201,10 @@ export const writeCreateTemplateDependenciesEffect = Effect.fn("Dependencies.wri
dependencies: target.dependencies,
devDependencies: target.devDependencies,
customDependencies: target.customDependencies,
scripts: getComposerScriptMap(opts.packageManager),
scripts:
target.packageJsonPath === "package.json"
? getComposerScriptMap(opts.packageManager)
: undefined,
projectDir: path.join(projectDir, path.dirname(target.packageJsonPath)),
});
}
Expand Down
10 changes: 6 additions & 4 deletions src/tasks/prisma-setup/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { log } from "@clack/prompts";
import { Effect, FileSystem } from "effect";
import path from "node:path";

import type { AuthoringStyle, DatabaseProvider } from "../../types";
import { getCreatePrismaSourceDir } from "../../templates/render-create-template";
import type { AuthoringStyle, CreateTemplate, DatabaseProvider } from "../../types";
import { getLocalPackageBinaryArgs } from "../../utils/package-manager";
import { redactSecrets } from "../../utils/errors";
import { runPrismaJsonCommandEffect } from "../prisma-cli";
import type { PrismaSetupContext } from "./types";

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

const getInitTarget = (provider: DatabaseProvider) =>
provider === "mongo" ? ("mongodb" as const) : ("postgres" as const);
Expand Down Expand Up @@ -43,6 +44,7 @@ export const runPrismaInit = Effect.fn("PrismaSetup.init")(function* (
context: PrismaSetupContext,
projectDir: string,
force = false,
template: CreateTemplate = "minimal",
) {
yield* runPrismaCli(context, projectDir, [
"orm",
Expand All @@ -54,7 +56,7 @@ export const runPrismaInit = Effect.fn("PrismaSetup.init")(function* (
"--authoring",
context.authoring,
"--schema-path",
getContractPath(context.authoring),
getContractPath(context.authoring, template),
"--skip-install",
]);
if (context.packageManager === "deno") {
Expand Down
4 changes: 2 additions & 2 deletions src/tasks/setup-prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const executePrismaSetupContextEffect = Effect.fn("PrismaSetup.execute")(
context.packageManager,
context.authoring,
projectDir,
{ skillsSync: context.skillAgents.length > 0 },
{ skillsSync: context.skillAgents.length > 0, template },
),
"configure_project",
"project_configuration_failed",
Expand All @@ -85,7 +85,7 @@ export const executePrismaSetupContextEffect = Effect.fn("PrismaSetup.execute")(

yield* Effect.sync(() => progress?.message("Preparing Prisma 8 project files..."));
yield* atCreateStage(
runPrismaInit(context, projectDir, options.force),
runPrismaInit(context, projectDir, options.force, template),
"initialize_prisma",
"prisma_init_failed",
);
Expand Down
23 changes: 23 additions & 0 deletions src/templates/render-create-template.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Effect } from "effect";
import path from "node:path";

import { applicationRuntime } from "../runtime";
import {
Expand All @@ -11,6 +12,13 @@ import {
} from "../types";
import { renderTemplateTreeEffect, resolveTemplatesDirEffect } from "./shared";

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

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

type CreateTemplateContext = {
projectName: string;
template: CreateTemplate;
Expand All @@ -37,6 +45,7 @@ const tsdownEntries: Partial<Record<CreateTemplate, string>> = {
hono: "src/index.ts",
elysia: "src/index.ts",
nest: "src/main.ts",
turborepo: "src/index.ts",
};

function createTemplateContext(options: ScaffoldCreateTemplateOptions): CreateTemplateContext {
Expand All @@ -59,6 +68,20 @@ export const scaffoldCreateSharedTemplatesEffect = Effect.fn("Templates.scaffold
templateRoot,
outputDir: options.projectDir,
context: createTemplateContext(options),
mapRelativeOutputPath(relativePath) {
if (options.template !== "turborepo") return relativePath;
if (relativePath === "tsdown.config.ts") {
return path.join("apps/server", relativePath);
}
const relativePrismaPath = path.relative(DEFAULT_PRISMA_SOURCE_DIR, relativePath);
if (
relativePrismaPath === ".." ||
relativePrismaPath.startsWith(`..${path.sep}`) ||
path.isAbsolute(relativePrismaPath)
)
return relativePath;
return path.join(TURBOREPO_PRISMA_SOURCE_DIR, relativePrismaPath);
},
});
});

Expand Down
13 changes: 11 additions & 2 deletions src/templates/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,26 @@ export const renderTemplateFileEffect = Effect.fn("Templates.renderFile")(functi

export const renderTemplateTreeEffect = Effect.fn("Templates.renderTree")(function* <
TContext,
>(opts: { templateRoot: string; outputDir: string; context: TContext }) {
>(opts: {
templateRoot: string;
outputDir: string;
context: TContext;
mapRelativeOutputPath?: (relativePath: string) => string;
}) {
const fs = yield* FileSystem.FileSystem;
const entries = yield* fs.readDirectory(opts.templateRoot, { recursive: true });

for (const relativePath of entries) {
const templateFilePath = path.join(opts.templateRoot, relativePath);
const info = yield* fs.stat(templateFilePath);
if (info.type !== "File") continue;
const renderedRelativePath = stripHbsExtension(relativePath);
yield* renderTemplateFileEffect({
templateFilePath,
outputPath: path.join(opts.outputDir, stripHbsExtension(relativePath)),
outputPath: path.join(
opts.outputDir,
opts.mapRelativeOutputPath?.(renderedRelativePath) ?? renderedRelativePath,
),
context: opts.context,
});
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const createTemplates = [
"elysia",
"nest",
"next",
"turborepo",
"svelte",
"astro",
"nuxt",
Expand Down
5 changes: 5 additions & 0 deletions templates/create/_package-manager/pnpm-workspace.yaml.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{{#if (eq packageManager "pnpm")}}
{{#if (eq template "turborepo")}}
packages:
- "apps/*"
- "packages/*"
{{/if}}
allowBuilds:
esbuild: true
msgpackr-extract: true
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 minimal Prisma 8 monorepo with Turborepo and a plain Node.js HTTP server.

## Workspace layout

- `apps/server` — Hello World server; `/users` reads from the shared database package
- `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 builds the server with tsdown, provisions Prisma Postgres when selected, applies migrations, and deploys the server to Prisma Compute.

The home page returns `Hello World!` without querying the database. Visiting `/users` queries the shared Prisma package and inserts starter users idempotently from `packages/database/src/seed.ts`.

{{#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
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: [{{#each skillAgents}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}],
},
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
4 changes: 3 additions & 1 deletion templates/create/_shared/service.ts.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { compute } from "@prisma/composer-prisma-cloud";
{{#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";
{{/if}}

export default compute({
Expand All @@ -29,6 +29,8 @@ export default compute({
{{/if}}
{{#if (eq template "next")}}
build: nextjs({ module: import.meta.url, appDir: "." }),
{{else if (eq template "turborepo")}}
build: node({ module: import.meta.url, entry: "./apps/server/dist/server.mjs" }),
{{else if (eq template "svelte")}}
build: node({ module: import.meta.url, dir: "./build", entry: "index.js" }),
{{else if (eq template "astro")}}
Expand Down
Loading
Loading