From d096cc23d0fd03f21a8c7642632492dde9eab555 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Thu, 27 Aug 2026 21:39:02 +0530 Subject: [PATCH 1/7] feat: add Turborepo monorepo template --- README.md | 3 +- src/commands/create.ts | 5 ++ src/constants/dependencies.ts | 15 +++- src/tasks/install.ts | 11 +++ src/tasks/setup-prisma.ts | 22 ++++-- src/templates/render-create-template.ts | 28 +++++++ src/templates/shared.ts | 9 ++- src/types.ts | 1 + templates/create/_shared/.gitattributes.hbs | 12 +-- templates/create/_shared/README.md.hbs | 44 +++++++++++ templates/create/_shared/module.ts.hbs | 2 +- .../create/_shared/pnpm-workspace.yaml.hbs | 7 +- .../_shared/prisma-composer.config.ts.hbs | 4 +- templates/create/_shared/prisma.config.ts.hbs | 8 +- templates/create/_shared/service.ts.hbs | 6 +- templates/create/_shared/src/prisma/db.ts.hbs | 4 +- templates/create/next/.yarnrc.yml.hbs | 2 + templates/create/next/next.config.ts | 7 -- templates/create/next/next.config.ts.hbs | 19 +++++ templates/create/next/package.json.hbs | 10 ++- templates/create/next/src/app/page.tsx.hbs | 8 +- templates/create/turborepo/.gitignore | 10 +++ templates/create/turborepo/.yarnrc.yml.hbs | 3 + templates/create/turborepo/package.json.hbs | 21 ++++++ .../turborepo/packages/database/package.json | 13 ++++ .../turborepo/packages/database/tsconfig.json | 4 + templates/create/turborepo/tsconfig.json | 22 ++++++ templates/create/turborepo/turbo.json | 24 ++++++ tests/dependencies.test.ts | 2 + tests/e2e/create-prisma.e2e.test.ts | 73 ++++++++++++++++++- tests/install.test.ts | 68 +++++++++++++---- 31 files changed, 409 insertions(+), 58 deletions(-) delete mode 100644 templates/create/next/next.config.ts create mode 100644 templates/create/next/next.config.ts.hbs create mode 100644 templates/create/turborepo/.gitignore create mode 100644 templates/create/turborepo/.yarnrc.yml.hbs create mode 100644 templates/create/turborepo/package.json.hbs create mode 100644 templates/create/turborepo/packages/database/package.json create mode 100644 templates/create/turborepo/packages/database/tsconfig.json create mode 100644 templates/create/turborepo/tsconfig.json create mode 100644 templates/create/turborepo/turbo.json diff --git a/README.md b/README.md index e82633a..cba79f5 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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` diff --git a/src/commands/create.ts b/src/commands/create.ts index 003128c..be43114 100644 --- a/src/commands/create.ts +++ b/src/commands/create.ts @@ -159,6 +159,11 @@ async function promptForCreateTemplate(output: Writable): Promise { const dependencies = [getDbPackages(provider)]; if (provider === "postgres" && packageManager !== "deno") dependencies.push("temporal-polyfill"); @@ -149,6 +150,7 @@ export async function writePrismaDependencies( if (packageManager === "deno") dependencies.push("dotenv"); const devDependencies = ["@types/node", "prisma"]; + if (packageManager !== "deno") devDependencies.push("@prisma/dev"); await addPackageDependency({ dependencies, @@ -156,6 +158,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: { diff --git a/src/tasks/setup-prisma.ts b/src/tasks/setup-prisma.ts index 4ace2e3..023b531 100644 --- a/src/tasks/setup-prisma.ts +++ b/src/tasks/setup-prisma.ts @@ -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, @@ -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" { @@ -235,7 +240,11 @@ function getPrismaCliInvocation(packageManager: PackageManager, args: string[]) return getPackageExecutionArgs(packageManager, [packageName, ...args]); } -async function runPrismaInit(context: PrismaSetupContext, projectDir: string): Promise { +async function runPrismaInit( + context: PrismaSetupContext, + projectDir: string, + template: CreateTemplate, +): Promise { const args = [ "orm", "init", @@ -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); @@ -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"; @@ -477,6 +486,7 @@ export async function executePrismaSetupContext( context.packageManager, context.authoring, projectDir, + template, ); await ensureComposerTypeScriptOptions(projectDir); if (context.databaseProvider === "mongo") await ensureMongoEnvironment(projectDir); diff --git a/src/templates/render-create-template.ts b/src/templates/render-create-template.ts index fe4cd98..31d1cb7 100644 --- a/src/templates/render-create-template.ts +++ b/src/templates/render-create-template.ts @@ -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; @@ -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, @@ -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); + }, }); } @@ -77,4 +98,11 @@ export async function scaffoldCreateFrameworkTemplate(opts: { outputDir: projectDir, context, }); + if (template === "turborepo") { + await renderTemplateTree({ + templateRoot: getCreateTemplateDir("next"), + outputDir: path.join(projectDir, "apps/web"), + context, + }); + } } diff --git a/src/templates/shared.ts b/src/templates/shared.ts index cc0126f..37b6639 100644 --- a/src/templates/shared.ts +++ b/src/templates/shared.ts @@ -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) => @@ -126,13 +127,17 @@ export async function renderTemplateTree(opts: { templateRoot: string; outputDir: string; context: TContext; + mapRelativeOutputPath?: (relativePath: string) => string; }): Promise { - 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, diff --git a/src/types.ts b/src/types.ts index 55e5446..8fbeb25 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export const createTemplates = [ "elysia", "nest", "next", + "turborepo", "svelte", "astro", "nuxt", diff --git a/templates/create/_shared/.gitattributes.hbs b/templates/create/_shared/.gitattributes.hbs index fe00c01..298270d 100644 --- a/templates/create/_shared/.gitattributes.hbs +++ b/templates/create/_shared/.gitattributes.hbs @@ -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 diff --git a/templates/create/_shared/README.md.hbs b/templates/create/_shared/README.md.hbs index 8e79b84..ef42d32 100644 --- a/templates/create/_shared/README.md.hbs +++ b/templates/create/_shared/README.md.hbs @@ -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. diff --git a/templates/create/_shared/module.ts.hbs b/templates/create/_shared/module.ts.hbs index 6c24897..72b060d 100644 --- a/templates/create/_shared/module.ts.hbs +++ b/templates/create/_shared/module.ts.hbs @@ -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}} diff --git a/templates/create/_shared/pnpm-workspace.yaml.hbs b/templates/create/_shared/pnpm-workspace.yaml.hbs index ca1c4ac..8ec37a4 100644 --- a/templates/create/_shared/pnpm-workspace.yaml.hbs +++ b/templates/create/_shared/pnpm-workspace.yaml.hbs @@ -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")}} diff --git a/templates/create/_shared/prisma-composer.config.ts.hbs b/templates/create/_shared/prisma-composer.config.ts.hbs index e4fe7cc..d087eac 100644 --- a/templates/create/_shared/prisma-composer.config.ts.hbs +++ b/templates/create/_shared/prisma-composer.config.ts.hbs @@ -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}} diff --git a/templates/create/_shared/prisma.config.ts.hbs b/templates/create/_shared/prisma.config.ts.hbs index 847929e..4fa7618 100644 --- a/templates/create/_shared/prisma.config.ts.hbs +++ b/templates/create/_shared/prisma.config.ts.hbs @@ -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}}!, @@ -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}}!, diff --git a/templates/create/_shared/service.ts.hbs b/templates/create/_shared/service.ts.hbs index 3866e57..0369156 100644 --- a/templates/create/_shared/service.ts.hbs +++ b/templates/create/_shared/service.ts.hbs @@ -1,5 +1,5 @@ {{#unless (eq packageManager "deno")}} -{{#if (eq template "next")}} +{{#if (or (eq template "next") (eq template "turborepo"))}} import nextjs from "@prisma/composer/nextjs"; {{else}} import node from "@prisma/composer/node"; @@ -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({ @@ -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: nextjs({ module: import.meta.url, appDir: "./apps/web" }), {{else if (eq template "svelte")}} build: node({ module: import.meta.url, dir: "./build", entry: "index.js" }), {{else if (eq template "astro")}} diff --git a/templates/create/_shared/src/prisma/db.ts.hbs b/templates/create/_shared/src/prisma/db.ts.hbs index 26e0e9d..576d3d0 100644 --- a/templates/create/_shared/src/prisma/db.ts.hbs +++ b/templates/create/_shared/src/prisma/db.ts.hbs @@ -14,7 +14,7 @@ export const db = postgres({ contractJson, url: databaseUrl }); {{else}} import "temporal-polyfill/global"; -import service from "../../service.ts"; +import service from "{{#if (eq template "turborepo")}}../../../service.ts{{else}}../../service.ts{{/if}}"; import type { Contract } from "./{{#if (eq authoring "typescript")}}generated/{{/if}}contract.d.ts"; import contractJson from "./{{#if (eq authoring "typescript")}}generated/{{/if}}contract.json" with { type: "json" }; @@ -35,7 +35,7 @@ export const db = {{else}} import mongo from "@prisma/orm-mongo/runtime"; -import service from "../../service.ts"; +import service from "{{#if (eq template "turborepo")}}../../../service.ts{{else}}../../service.ts{{/if}}"; import type { Contract } from "./{{#if (eq authoring "typescript")}}generated/{{/if}}contract.d.ts"; import contractJson from "./{{#if (eq authoring "typescript")}}generated/{{/if}}contract.json" with { type: "json" }; diff --git a/templates/create/next/.yarnrc.yml.hbs b/templates/create/next/.yarnrc.yml.hbs index faa4013..ce95a41 100644 --- a/templates/create/next/.yarnrc.yml.hbs +++ b/templates/create/next/.yarnrc.yml.hbs @@ -1,3 +1,5 @@ {{#if (eq packageManager "yarn")}} +{{#unless (eq template "turborepo")}} nodeLinker: node-modules +{{/unless}} {{/if}} diff --git a/templates/create/next/next.config.ts b/templates/create/next/next.config.ts deleted file mode 100644 index 68a6c64..0000000 --- a/templates/create/next/next.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { - output: "standalone", -}; - -export default nextConfig; diff --git a/templates/create/next/next.config.ts.hbs b/templates/create/next/next.config.ts.hbs new file mode 100644 index 0000000..2b863ea --- /dev/null +++ b/templates/create/next/next.config.ts.hbs @@ -0,0 +1,19 @@ +{{#if (eq template "turborepo")}} +import path from "node:path"; +import { fileURLToPath } from "node:url"; +{{/if}} +import type { NextConfig } from "next"; + +{{#if (eq template "turborepo")}} +const monorepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +{{/if}} + +const nextConfig: NextConfig = { + output: "standalone", +{{#if (eq template "turborepo")}} + outputFileTracingRoot: monorepoRoot, + transpilePackages: ["@repo/database"], +{{/if}} +}; + +export default nextConfig; diff --git a/templates/create/next/package.json.hbs b/templates/create/next/package.json.hbs index 2a5336e..9cdd85b 100644 --- a/templates/create/next/package.json.hbs +++ b/templates/create/next/package.json.hbs @@ -1,18 +1,24 @@ { - "name": "{{projectName}}", + "name": "{{#if (eq template "turborepo")}}@repo/web{{else}}{{projectName}}{{/if}}", "version": "0.1.0", "private": true, + {{#unless (eq template "turborepo")}} {{#if (packageManagerManifestValue packageManager)}} "packageManager": "{{packageManagerManifestValue packageManager}}", {{/if}} + {{/unless}} "type": "module", "scripts": { "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "typecheck": "tsc --noEmit" }, "dependencies": { + {{#if (eq template "turborepo")}} + "@repo/database": "{{#if (eq packageManager "npm")}}*{{else}}workspace:*{{/if}}", + {{/if}} "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3" diff --git a/templates/create/next/src/app/page.tsx.hbs b/templates/create/next/src/app/page.tsx.hbs index eaf1828..07015f9 100644 --- a/templates/create/next/src/app/page.tsx.hbs +++ b/templates/create/next/src/app/page.tsx.hbs @@ -2,7 +2,7 @@ export const dynamic = "force-dynamic"; export default async function Home() { - const { listUsers } = await import("../prisma/users"); + const { listUsers } = await import("{{#if (eq template "turborepo")}}@repo/database{{else}}../prisma/users{{/if}}"); const formatter = new Intl.DateTimeFormat("en", { dateStyle: "medium", timeStyle: "short", @@ -12,12 +12,12 @@ export default async function Home() { return (
-

Next.js + Prisma 8

+

{{#if (eq template "turborepo")}}Turborepo + {{/if}}Next.js + Prisma 8

Users from your database, loaded on the server.

- This page reads from src/app/page.tsx using the Prisma 8 helper in{" "} - src/prisma/users.ts. + This page reads from {{#if (eq template "turborepo")}}apps/web/{{/if}}src/app/page.tsx using the Prisma 8 helper in{" "} + {{#if (eq template "turborepo")}}packages/database/src/users.ts{{else}}src/prisma/users.ts{{/if}}.

diff --git a/templates/create/turborepo/.gitignore b/templates/create/turborepo/.gitignore new file mode 100644 index 0000000..18e47e2 --- /dev/null +++ b/templates/create/turborepo/.gitignore @@ -0,0 +1,10 @@ +node_modules +.turbo +apps/web/.next +apps/web/out +coverage +.env +.env.* +!.env.example +*.tsbuildinfo +.DS_Store diff --git a/templates/create/turborepo/.yarnrc.yml.hbs b/templates/create/turborepo/.yarnrc.yml.hbs new file mode 100644 index 0000000..faa4013 --- /dev/null +++ b/templates/create/turborepo/.yarnrc.yml.hbs @@ -0,0 +1,3 @@ +{{#if (eq packageManager "yarn")}} +nodeLinker: node-modules +{{/if}} diff --git a/templates/create/turborepo/package.json.hbs b/templates/create/turborepo/package.json.hbs new file mode 100644 index 0000000..8f63c5a --- /dev/null +++ b/templates/create/turborepo/package.json.hbs @@ -0,0 +1,21 @@ +{ + "name": "{{projectName}}", + "version": "0.1.0", + "private": true, + {{#if (packageManagerManifestValue packageManager)}} + "packageManager": "{{packageManagerManifestValue packageManager}}", + {{/if}} + "type": "module", + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "build": "turbo run build", + "dev": "turbo run dev --parallel", + "lint": "turbo run lint", + "start": "turbo run start --parallel", + "typecheck": "turbo run typecheck" + }, + "devDependencies": {} +} diff --git a/templates/create/turborepo/packages/database/package.json b/templates/create/turborepo/packages/database/package.json new file mode 100644 index 0000000..9aee6b2 --- /dev/null +++ b/templates/create/turborepo/packages/database/package.json @@ -0,0 +1,13 @@ +{ + "name": "@repo/database", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/users.ts", + "./db": "./src/db.ts" + }, + "scripts": { + "typecheck": "tsc --noEmit --project tsconfig.json" + } +} diff --git a/templates/create/turborepo/packages/database/tsconfig.json b/templates/create/turborepo/packages/database/tsconfig.json new file mode 100644 index 0000000..a5cb75c --- /dev/null +++ b/templates/create/turborepo/packages/database/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts"] +} diff --git a/templates/create/turborepo/tsconfig.json b/templates/create/turborepo/tsconfig.json new file mode 100644 index 0000000..5b74628 --- /dev/null +++ b/templates/create/turborepo/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "types": ["node"] + }, + "include": [ + "module.ts", + "service.ts", + "prisma.config.ts", + "prisma-composer.config.ts", + "packages/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/templates/create/turborepo/turbo.json b/templates/create/turborepo/turbo.json new file mode 100644 index 0000000..4a8a491 --- /dev/null +++ b/templates/create/turborepo/turbo.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": [".next/**", "!.next/cache/**", "dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "lint": { + "dependsOn": ["^lint"] + }, + "start": { + "cache": false, + "dependsOn": ["build"], + "persistent": true + }, + "typecheck": { + "dependsOn": ["^typecheck"] + } + } +} diff --git a/tests/dependencies.test.ts b/tests/dependencies.test.ts index f6f021a..b8a8e7d 100644 --- a/tests/dependencies.test.ts +++ b/tests/dependencies.test.ts @@ -8,9 +8,11 @@ describe("Prisma 8 dependency versions", () => { expect(getDependencyVersion("@prisma/orm-mongo")).toBe("8.0.0-rc.8"); expect(getDependencyVersion("@prisma/composer")).toBe("0.16.0"); expect(getDependencyVersion("@prisma/composer-prisma-cloud")).toBe("0.16.0"); + expect(getDependencyVersion("@prisma/dev")).toBe("0.24.7"); expect(getDependencyVersion("prisma")).toBe("8.0.0-rc.12"); expect(getDependencyVersion("alchemy")).toBe("2.0.0-beta.74"); expect(getDependencyVersion("effect")).toBe("4.0.0-rc.112"); + expect(getDependencyVersion("turbo")).toBe("2.10.12"); }); test("returns undefined for dependencies missing from the version map", () => { diff --git a/tests/e2e/create-prisma.e2e.test.ts b/tests/e2e/create-prisma.e2e.test.ts index 5e56291..5a958d6 100644 --- a/tests/e2e/create-prisma.e2e.test.ts +++ b/tests/e2e/create-prisma.e2e.test.ts @@ -96,7 +96,13 @@ async function fetchUntilReady(url: string, deadline: number): Promise } } -async function verifyComposerDev(projectDir: string) { +async function verifyComposerDev( + projectDir: string, + verifyResponse: (response: Response) => Promise = async (response) => { + const body = (await response.json()) as { users: Array<{ name: string }> }; + expect(body.users.map((user) => user.name)).toEqual(["Alice", "Bob", "Carol"]); + }, +) { const process = Bun.spawn({ cmd: ["bun", "run", "dev:composer"], cwd: projectDir, @@ -139,8 +145,7 @@ async function verifyComposerDev(projectDir: string) { // retry connection refusals until the deadline. const response = await fetchUntilReady(appUrl, deadline); expect(response.status).toBe(200); - const body = (await response.json()) as { users: Array<{ name: string }> }; - expect(body.users.map((user) => user.name)).toEqual(["Alice", "Bob", "Carol"]); + await verifyResponse(response); return; } } finally { @@ -427,6 +432,68 @@ describe("create-prisma e2e", () => { TEST_TIMEOUT, ); + test( + "builds and runs a Composer-backed Turborepo monorepo", + async () => { + const rootDir = await mkdtemp(path.join(tmpdir(), "create-prisma-turborepo-e2e-")); + tempRoots.push(rootDir); + const previousCwd = process.cwd(); + process.chdir(rootDir); + try { + await runCreateCommand({ + name: "turborepo-app", + template: "turborepo", + provider: "postgres", + authoring: "psl", + packageManager: "bun", + deploy: false, + yes: true, + }); + } finally { + process.chdir(previousCwd); + } + + const projectDir = path.join(rootDir, "turborepo-app"); + const rootPackageJson = JSON.parse( + await readFile(path.join(projectDir, "package.json"), "utf8"), + ) as Record; + const webPackageJson = JSON.parse( + await readFile(path.join(projectDir, "apps/web/package.json"), "utf8"), + ) as Record; + const databasePackageJson = JSON.parse( + await readFile(path.join(projectDir, "packages/database/package.json"), "utf8"), + ) as Record; + const serviceSource = await readFile(path.join(projectDir, "service.ts"), "utf8"); + + expect(rootPackageJson.workspaces).toEqual(["apps/*", "packages/*"]); + expect(rootPackageJson.devDependencies.turbo).toBe("2.10.12"); + expect(webPackageJson.dependencies["@repo/database"]).toBe("workspace:*"); + expect(databasePackageJson.dependencies["@prisma/orm-postgres"]).toBe("8.0.0-rc.8"); + expect(databasePackageJson.dependencies["temporal-polyfill"]).toBe("^1.0.4"); + expect(serviceSource).toContain('appDir: "./apps/web"'); + expect(await pathExists(path.join(projectDir, "packages/database/src/contract.json"))).toBe( + true, + ); + expect(await pathExists(path.join(projectDir, "migrations/app"))).toBe(true); + + await runCommand(projectDir, ["bun", "run", "build"]); + await runCommand(projectDir, ["bun", "run", "typecheck"]); + + const requiredServerFiles = JSON.parse( + await readFile(path.join(projectDir, "apps/web/.next/required-server-files.json"), "utf8"), + ) as { relativeAppDir?: string }; + expect(requiredServerFiles.relativeAppDir).toBe("apps/web"); + + await verifyComposerDev(projectDir, async (response) => { + const body = await response.text(); + expect(body).toContain("Alice"); + expect(body).toContain("Bob"); + expect(body).toContain("Carol"); + }); + }, + TEST_TIMEOUT, + ); + test( "generates and checks a minimal Deno Prisma Postgres app", async () => { diff --git a/tests/install.test.ts b/tests/install.test.ts index a84bfb6..2463ffd 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -18,6 +18,8 @@ import { } from "../src/utils/package-manager"; type PackageJson = { + name?: string; + workspaces?: string[]; dependencies?: Record; devDependencies?: Record; scripts?: Record; @@ -57,6 +59,7 @@ describe("writePrismaDependencies", () => { }); expect(packageJson.dependencies?.dotenv).toBeUndefined(); expect(packageJson.devDependencies).toMatchObject({ + "@prisma/dev": dependencyVersionMap["@prisma/dev"], prisma: dependencyVersionMap.prisma, }); expect(packageJson.scripts).toMatchObject({ @@ -95,6 +98,7 @@ describe("writePrismaDependencies", () => { "@types/node": dependencyVersionMap["@types/node"], prisma: dependencyVersionMap.prisma, }); + expect(packageJson.devDependencies?.["@prisma/dev"]).toBeUndefined(); expect(packageJson.devDependencies?.["@prisma/cli-engine"]).toBeUndefined(); expect(packageJson.scripts).toMatchObject({ "contract:emit": `deno run -A npm:${PRISMA_DENO_CLI_PACKAGE} contract emit`, @@ -156,15 +160,13 @@ describe("generated templates", () => { await writeCreateTemplateDependencies({ template, packageManager, projectDir }); const packageJson = await readPackageJson(projectDir); - const dbSource = await readFile(path.join(projectDir, "src/prisma/db.ts"), "utf8"); - const seedSource = await readFile( - path.join(projectDir, "src/prisma/seed.ts"), - "utf8", - ); - const usersSource = await readFile( - path.join(projectDir, "src/prisma/users.ts"), - "utf8", + const prismaSourceDir = path.join( + projectDir, + template === "turborepo" ? "packages/database/src" : "src/prisma", ); + const dbSource = await readFile(path.join(prismaSourceDir, "db.ts"), "utf8"); + const seedSource = await readFile(path.join(prismaSourceDir, "seed.ts"), "utf8"); + const usersSource = await readFile(path.join(prismaSourceDir, "users.ts"), "utf8"); const tsconfig = await readFile(path.join(projectDir, "tsconfig.json"), "utf8"); if (packageManager === "deno") { @@ -209,9 +211,38 @@ describe("generated templates", () => { expect(seedSource).toContain("await connectDatabase()"); expect(seedSource).toContain("export function seed()"); expect(usersSource).toContain("await seed()"); - expect(await pathExists(path.join(projectDir, "src/prisma/starter-data.ts"))).toBe( - false, - ); + expect(await pathExists(path.join(prismaSourceDir, "starter-data.ts"))).toBe(false); + if (template === "turborepo") { + const webPackageJson = await readPackageJson(path.join(projectDir, "apps/web")); + const databasePackageJson = await readPackageJson( + path.join(projectDir, "packages/database"), + ); + const nextConfig = await readFile( + path.join(projectDir, "apps/web/next.config.ts"), + "utf8", + ); + const pageSource = await readFile( + path.join(projectDir, "apps/web/src/app/page.tsx"), + "utf8", + ); + + expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); + expect(packageJson.devDependencies?.turbo).toBe(dependencyVersionMap.turbo); + expect(webPackageJson.name).toBe("@repo/web"); + expect(webPackageJson.dependencies?.["@repo/database"]).toBe( + packageManager === "npm" ? "*" : "workspace:*", + ); + expect(databasePackageJson.name).toBe("@repo/database"); + expect(databasePackageJson.devDependencies?.typescript).toBe( + dependencyVersionMap.typescript, + ); + expect(nextConfig).toContain("outputFileTracingRoot: monorepoRoot"); + expect(nextConfig).toContain('transpilePackages: ["@repo/database"]'); + expect(pageSource).toContain('import("@repo/database")'); + expect(serviceSource).toContain('appDir: "./apps/web"'); + expect(dbSource).toContain('import service from "../../../service.ts"'); + expect(await pathExists(path.join(projectDir, "src/prisma"))).toBe(false); + } if (template === "elysia") { const serverSource = await readFile(path.join(projectDir, "src/index.ts"), "utf8"); expect(serverSource).toContain('adapter: "Bun" in globalThis ? undefined : node()'); @@ -245,7 +276,7 @@ describe("generated templates", () => { expect(prismaConfig).toContain("connection: process.env.DATABASE_URL!"); expect(seedSource).toContain("conflictOn: { email: user.email }"); const composerSource = await readFile( - path.join(projectDir, "src/prisma/composer.ts"), + path.join(prismaSourceDir, "composer.ts"), "utf8", ); if (authoring === "typescript") { @@ -267,7 +298,13 @@ describe("generated templates", () => { expect(seedSource).not.toContain(".prisma-composer"); } if (authoring === "typescript") { - expect(prismaConfig).toContain('output: "./src/prisma/generated"'); + expect(prismaConfig).toContain( + `output: "./${ + template === "turborepo" + ? "packages/database/src/generated" + : "src/prisma/generated" + }"`, + ); expect(dbSource).toContain( 'import type { Contract } from "./generated/contract.d.ts";', ); @@ -282,13 +319,16 @@ describe("generated templates", () => { if (packageManager === "pnpm") { expect(packageJson.pnpm).toBeUndefined(); const frameworkBuildAllowances = - template === "next" + template === "next" || template === "turborepo" ? [" sharp: true", " unrs-resolver: true"] : template === "astro" ? [" sharp: true"] : []; expect(await readFile(path.join(projectDir, "pnpm-workspace.yaml"), "utf8")).toBe( [ + ...(template === "turborepo" + ? ["packages:", ' - "apps/*"', ' - "packages/*"'] + : []), "allowBuilds:", " esbuild: true", " msgpackr-extract: true", From 4fa2fbe6e09027a8dc248cdd87fee38cf5cc8ba0 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Thu, 27 Aug 2026 22:38:19 +0530 Subject: [PATCH 2/7] fix: keep monorepo dependencies package-owned --- src/constants/dependencies.ts | 1 - src/tasks/install.ts | 10 +++++++--- tests/dependencies.test.ts | 1 - tests/install.test.ts | 5 +++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/constants/dependencies.ts b/src/constants/dependencies.ts index dc3a839..32319a6 100644 --- a/src/constants/dependencies.ts +++ b/src/constants/dependencies.ts @@ -5,7 +5,6 @@ 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", diff --git a/src/tasks/install.ts b/src/tasks/install.ts index 57a89fb..c9ca90d 100644 --- a/src/tasks/install.ts +++ b/src/tasks/install.ts @@ -145,12 +145,13 @@ export async function writePrismaDependencies( template?: CreateTemplate, ): Promise { 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"); const devDependencies = ["@types/node", "prisma"]; - if (packageManager !== "deno") devDependencies.push("@prisma/dev"); await addPackageDependency({ dependencies, @@ -185,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)), }); } diff --git a/tests/dependencies.test.ts b/tests/dependencies.test.ts index b8a8e7d..986d6cf 100644 --- a/tests/dependencies.test.ts +++ b/tests/dependencies.test.ts @@ -8,7 +8,6 @@ describe("Prisma 8 dependency versions", () => { expect(getDependencyVersion("@prisma/orm-mongo")).toBe("8.0.0-rc.8"); expect(getDependencyVersion("@prisma/composer")).toBe("0.16.0"); expect(getDependencyVersion("@prisma/composer-prisma-cloud")).toBe("0.16.0"); - expect(getDependencyVersion("@prisma/dev")).toBe("0.24.7"); expect(getDependencyVersion("prisma")).toBe("8.0.0-rc.12"); expect(getDependencyVersion("alchemy")).toBe("2.0.0-beta.74"); expect(getDependencyVersion("effect")).toBe("4.0.0-rc.112"); diff --git a/tests/install.test.ts b/tests/install.test.ts index 2463ffd..6a8ec32 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -59,7 +59,6 @@ describe("writePrismaDependencies", () => { }); expect(packageJson.dependencies?.dotenv).toBeUndefined(); expect(packageJson.devDependencies).toMatchObject({ - "@prisma/dev": dependencyVersionMap["@prisma/dev"], prisma: dependencyVersionMap.prisma, }); expect(packageJson.scripts).toMatchObject({ @@ -98,7 +97,6 @@ describe("writePrismaDependencies", () => { "@types/node": dependencyVersionMap["@types/node"], prisma: dependencyVersionMap.prisma, }); - expect(packageJson.devDependencies?.["@prisma/dev"]).toBeUndefined(); expect(packageJson.devDependencies?.["@prisma/cli-engine"]).toBeUndefined(); expect(packageJson.scripts).toMatchObject({ "contract:emit": `deno run -A npm:${PRISMA_DENO_CLI_PACKAGE} contract emit`, @@ -236,6 +234,9 @@ describe("generated templates", () => { expect(databasePackageJson.devDependencies?.typescript).toBe( dependencyVersionMap.typescript, ); + expect(databasePackageJson.scripts).toEqual({ + typecheck: "tsc --noEmit --project tsconfig.json", + }); expect(nextConfig).toContain("outputFileTracingRoot: monorepoRoot"); expect(nextConfig).toContain('transpilePackages: ["@repo/database"]'); expect(pageSource).toContain('import("@repo/database")'); From eb1d9899d8e3d5927c6effe0695a8051efde7a01 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 15 Sep 2026 00:39:26 +0530 Subject: [PATCH 3/7] test: preserve the Windows Next.js smoke test selector --- tests/e2e/create-prisma.e2e.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/e2e/create-prisma.e2e.test.ts b/tests/e2e/create-prisma.e2e.test.ts index 37660e0..728f6cc 100644 --- a/tests/e2e/create-prisma.e2e.test.ts +++ b/tests/e2e/create-prisma.e2e.test.ts @@ -567,9 +567,12 @@ describe("create-prisma e2e", () => { TEST_TIMEOUT, ); - test.each(["next", "turborepo"] as const)( + test.each([ + ["Next.js", "next"], + ["Turborepo", "turborepo"], + ] as const)( "builds a %s app with a TypeScript-authored contract", - async (template) => { + async (_label, template) => { const rootDir = await mkdtemp(path.join(tmpdir(), "create-prisma-next-typescript-e2e-")); tempRoots.push(rootDir); const previousCwd = process.cwd(); From 8b358ebe8ccd93dcf37b9d2fbc192c9ee4591d55 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 15 Sep 2026 00:52:34 +0530 Subject: [PATCH 4/7] fix: give the database workspace its MongoDB runtime dependencies --- src/tasks/install.ts | 15 +++++++-------- tests/install.test.ts | 11 +++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/tasks/install.ts b/src/tasks/install.ts index 0fb521b..f21721f 100644 --- a/src/tasks/install.ts +++ b/src/tasks/install.ts @@ -163,11 +163,13 @@ export const writePrismaDependenciesEffect = Effect.fn("Dependencies.writePrisma projectDir = process.cwd(), options: { skillsSync?: boolean; template?: CreateTemplate } = {}, ) { - const dependencies = [getDbPackages(provider)]; - if (provider === "postgres" && packageManager !== "deno" && options.template !== "turborepo") { - dependencies.push("temporal-polyfill"); + const databaseDependencies = [getDbPackages(provider)]; + if (provider === "postgres" && packageManager !== "deno") { + databaseDependencies.push("temporal-polyfill"); } - if (provider === "mongo") dependencies.push("arktype", "mongodb"); + if (provider === "mongo") databaseDependencies.push("arktype", "mongodb"); + const dependencies = + options.template === "turborepo" ? [getDbPackages(provider)] : [...databaseDependencies]; if (packageManager === "deno") dependencies.push("dotenv"); yield* addPackageDependencyEffect({ dependencies, @@ -177,10 +179,7 @@ export const writePrismaDependenciesEffect = Effect.fn("Dependencies.writePrisma }); if (options.template === "turborepo") { yield* addPackageDependencyEffect({ - dependencies: [ - getDbPackages(provider), - ...(provider === "postgres" ? ["temporal-polyfill"] : []), - ], + dependencies: databaseDependencies, projectDir: path.join(projectDir, "packages/database"), }); } diff --git a/tests/install.test.ts b/tests/install.test.ts index 9f9274d..284d225 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -346,6 +346,7 @@ describe("generated templates", () => { }); const web = await readPackageJson(path.join(projectDir, "apps/web")); const database = await readPackageJson(path.join(projectDir, "packages/database")); + const root = await readPackageJson(projectDir); expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); expect(packageJson.devDependencies?.turbo).toBe(dependencyVersionMap.turbo); expect(web.name).toBe("@repo/web"); @@ -359,6 +360,16 @@ describe("generated templates", () => { expect(database.dependencies?.["temporal-polyfill"]).toBe( provider === "postgres" ? dependencyVersionMap["temporal-polyfill"] : undefined, ); + for (const name of ["arktype", "mongodb"] as const) { + expect(database.dependencies?.[name]).toBe( + provider === "mongo" ? dependencyVersionMap[name] : undefined, + ); + expect(root.dependencies?.[name]).toBeUndefined(); + } + expect(root.dependencies?.["temporal-polyfill"]).toBeUndefined(); + expect(root.dependencies).toHaveProperty( + provider === "postgres" ? "@prisma/orm-postgres" : "@prisma/orm-mongo", + ); expect(database.scripts).toEqual({ typecheck: "tsc --noEmit --project tsconfig.json", }); From 43a4570912c7cba21bb5c2bd2fad7cc000547ecf Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 15 Sep 2026 00:55:21 +0530 Subject: [PATCH 5/7] fix: retain MongoDB service configuration dependencies at the root --- src/tasks/install.ts | 4 +++- tests/install.test.ts | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/tasks/install.ts b/src/tasks/install.ts index f21721f..92085b0 100644 --- a/src/tasks/install.ts +++ b/src/tasks/install.ts @@ -169,7 +169,9 @@ export const writePrismaDependenciesEffect = Effect.fn("Dependencies.writePrisma } if (provider === "mongo") databaseDependencies.push("arktype", "mongodb"); const dependencies = - options.template === "turborepo" ? [getDbPackages(provider)] : [...databaseDependencies]; + options.template === "turborepo" + ? [getDbPackages(provider), ...(provider === "mongo" ? ["arktype"] : [])] + : [...databaseDependencies]; if (packageManager === "deno") dependencies.push("dotenv"); yield* addPackageDependencyEffect({ dependencies, diff --git a/tests/install.test.ts b/tests/install.test.ts index 284d225..eac4b4e 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -364,7 +364,11 @@ describe("generated templates", () => { expect(database.dependencies?.[name]).toBe( provider === "mongo" ? dependencyVersionMap[name] : undefined, ); - expect(root.dependencies?.[name]).toBeUndefined(); + expect(root.dependencies?.[name]).toBe( + provider === "mongo" && name === "arktype" + ? dependencyVersionMap.arktype + : undefined, + ); } expect(root.dependencies?.["temporal-polyfill"]).toBeUndefined(); expect(root.dependencies).toHaveProperty( From bac3c0bbc414d5d5bc2791d0f408a4eefec79001 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 15 Sep 2026 13:28:52 +0530 Subject: [PATCH 6/7] feat: simplify the monorepo starter to a Hello World server --- README.md | 2 +- src/commands/create-context.ts | 2 +- src/constants/dependencies.ts | 12 +++++-- src/templates/render-create-template.ts | 12 +++---- src/templates/shared.ts | 1 - .../_package-manager/pnpm-workspace.yaml.hbs | 2 +- templates/create/_shared/README.md.hbs | 8 ++--- .../_shared/prisma-composer.config.ts.hbs | 4 +-- templates/create/_shared/service.ts.hbs | 4 +-- templates/create/next/.yarnrc.yml.hbs | 2 -- templates/create/next/next.config.ts | 7 +++++ templates/create/next/next.config.ts.hbs | 19 ------------ templates/create/next/package.json.hbs | 10 ++---- templates/create/next/src/app/page.tsx.hbs | 8 ++--- templates/create/turborepo/.gitignore | 3 +- .../create/turborepo/apps/server/package.json | 15 +++++++++ .../create/turborepo/apps/server/src/index.ts | 31 +++++++++++++++++++ .../turborepo/apps/server/tsconfig.json | 4 +++ templates/create/turborepo/package.json.hbs | 3 +- templates/create/turborepo/turbo.json | 7 ++--- tests/e2e/create-prisma.e2e.test.ts | 14 +++++++-- tests/install.test.ts | 24 +++++++++++--- 22 files changed, 124 insertions(+), 70 deletions(-) create mode 100644 templates/create/next/next.config.ts delete mode 100644 templates/create/next/next.config.ts.hbs create mode 100644 templates/create/turborepo/apps/server/package.json create mode 100644 templates/create/turborepo/apps/server/src/index.ts create mode 100644 templates/create/turborepo/apps/server/tsconfig.json diff --git a/README.md b/README.md index 9d7f9df..27b7989 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ workspace. Choosing another workspace also updates the Prisma CLI's active works - `elysia` - `nest` - `next` -- `turborepo` (Next.js in `apps/web`, shared Prisma package in `packages/database`) +- `turborepo` (Hello World Node.js server in `apps/server`, shared Prisma package in `packages/database`) - `svelte` (SvelteKit) - `astro` - `nuxt` diff --git a/src/commands/create-context.ts b/src/commands/create-context.ts index 4ff3311..9b3332e 100644 --- a/src/commands/create-context.ts +++ b/src/commands/create-context.ts @@ -92,7 +92,7 @@ const promptForCreateTemplate = Effect.fn("Prompts.template")(function* (output: { value: "turborepo", label: "Monorepo (Turborepo)", - hint: "Next.js app with a shared Prisma database package", + 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" }, diff --git a/src/constants/dependencies.ts b/src/constants/dependencies.ts index a38108d..0124ccd 100644 --- a/src/constants/dependencies.ts +++ b/src/constants/dependencies.ts @@ -55,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[] = []; @@ -80,7 +80,7 @@ export function getCreateTemplateDependencies( if (template === "tanstack-start") { devDependencies.push("nitro"); } - if (template === "turborepo") devDependencies.push("turbo"); + if (template === "turborepo") devDependencies.push("turbo", "typescript"); const targets: CreateTemplateDependencyTarget[] = [ { @@ -90,6 +90,14 @@ export function getCreateTemplateDependencies( }, ]; 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: [], diff --git a/src/templates/render-create-template.ts b/src/templates/render-create-template.ts index d1f4013..587c806 100644 --- a/src/templates/render-create-template.ts +++ b/src/templates/render-create-template.ts @@ -45,6 +45,7 @@ const tsdownEntries: Partial> = { hono: "src/index.ts", elysia: "src/index.ts", nest: "src/main.ts", + turborepo: "src/index.ts", }; function createTemplateContext(options: ScaffoldCreateTemplateOptions): CreateTemplateContext { @@ -69,6 +70,9 @@ export const scaffoldCreateSharedTemplatesEffect = Effect.fn("Templates.scaffold 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 === ".." || @@ -100,14 +104,6 @@ export const scaffoldCreateFrameworkTemplateEffect = Effect.fn("Templates.scaffo outputDir: options.projectDir, context: createTemplateContext(options), }); - if (options.template === "turborepo") { - const nextTemplateRoot = yield* resolveTemplatesDirEffect("templates/create/next"); - yield* renderTemplateTreeEffect({ - templateRoot: nextTemplateRoot, - outputDir: path.join(options.projectDir, "apps/web"), - context: createTemplateContext(options), - }); - } }, ); diff --git a/src/templates/shared.ts b/src/templates/shared.ts index 08f4dc6..6829d98 100644 --- a/src/templates/shared.ts +++ b/src/templates/shared.ts @@ -12,7 +12,6 @@ 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) => diff --git a/templates/create/_package-manager/pnpm-workspace.yaml.hbs b/templates/create/_package-manager/pnpm-workspace.yaml.hbs index 8ec37a4..7340f76 100644 --- a/templates/create/_package-manager/pnpm-workspace.yaml.hbs +++ b/templates/create/_package-manager/pnpm-workspace.yaml.hbs @@ -7,7 +7,7 @@ packages: allowBuilds: esbuild: true msgpackr-extract: true -{{#if (or (eq template "next") (eq template "turborepo"))}} +{{#if (eq template "next")}} sharp: true unrs-resolver: true {{else if (eq template "astro")}} diff --git a/templates/create/_shared/README.md.hbs b/templates/create/_shared/README.md.hbs index ef42d32..6563790 100644 --- a/templates/create/_shared/README.md.hbs +++ b/templates/create/_shared/README.md.hbs @@ -32,11 +32,11 @@ 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. +A minimal Prisma 8 monorepo with Turborepo and a plain Node.js HTTP server. ## Workspace layout -- `apps/web` — Next.js application +- `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 @@ -54,9 +54,9 @@ This builds the workspace and starts it with Composer. PostgreSQL projects get a {{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 deploy script builds the server with tsdown, provisions Prisma Postgres when selected, applies migrations, and deploys the server 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. +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. diff --git a/templates/create/_shared/prisma-composer.config.ts.hbs b/templates/create/_shared/prisma-composer.config.ts.hbs index 30e5abe..3fa0719 100644 --- a/templates/create/_shared/prisma-composer.config.ts.hbs +++ b/templates/create/_shared/prisma-composer.config.ts.hbs @@ -1,13 +1,13 @@ {{#unless (eq packageManager "deno")}} import { defineConfig } from "@prisma/composer/config"; import { nodeBuild } from "@prisma/composer/node/control"; -{{#if (or (eq template "next") (eq template "turborepo"))}} +{{#if (eq template "next")}} import { nextjsBuild } from "@prisma/composer/nextjs/control"; {{/if}} import { prismaCloud, prismaState } from "@prisma/composer-prisma-cloud/control"; export default defineConfig({ - extensions: [prismaCloud({ region: "us-east-1" }), nodeBuild(){{#if (or (eq template "next") (eq template "turborepo"))}}, nextjsBuild(){{/if}}], + extensions: [prismaCloud({ region: "us-east-1" }), nodeBuild(){{#if (eq template "next")}}, nextjsBuild(){{/if}}], state: prismaState(), }); {{/unless}} diff --git a/templates/create/_shared/service.ts.hbs b/templates/create/_shared/service.ts.hbs index 0369156..1a7d6be 100644 --- a/templates/create/_shared/service.ts.hbs +++ b/templates/create/_shared/service.ts.hbs @@ -1,5 +1,5 @@ {{#unless (eq packageManager "deno")}} -{{#if (or (eq template "next") (eq template "turborepo"))}} +{{#if (eq template "next")}} import nextjs from "@prisma/composer/nextjs"; {{else}} import node from "@prisma/composer/node"; @@ -30,7 +30,7 @@ export default compute({ {{#if (eq template "next")}} build: nextjs({ module: import.meta.url, appDir: "." }), {{else if (eq template "turborepo")}} - build: nextjs({ module: import.meta.url, appDir: "./apps/web" }), + 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")}} diff --git a/templates/create/next/.yarnrc.yml.hbs b/templates/create/next/.yarnrc.yml.hbs index ce95a41..faa4013 100644 --- a/templates/create/next/.yarnrc.yml.hbs +++ b/templates/create/next/.yarnrc.yml.hbs @@ -1,5 +1,3 @@ {{#if (eq packageManager "yarn")}} -{{#unless (eq template "turborepo")}} nodeLinker: node-modules -{{/unless}} {{/if}} diff --git a/templates/create/next/next.config.ts b/templates/create/next/next.config.ts new file mode 100644 index 0000000..68a6c64 --- /dev/null +++ b/templates/create/next/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: "standalone", +}; + +export default nextConfig; diff --git a/templates/create/next/next.config.ts.hbs b/templates/create/next/next.config.ts.hbs deleted file mode 100644 index 2b863ea..0000000 --- a/templates/create/next/next.config.ts.hbs +++ /dev/null @@ -1,19 +0,0 @@ -{{#if (eq template "turborepo")}} -import path from "node:path"; -import { fileURLToPath } from "node:url"; -{{/if}} -import type { NextConfig } from "next"; - -{{#if (eq template "turborepo")}} -const monorepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -{{/if}} - -const nextConfig: NextConfig = { - output: "standalone", -{{#if (eq template "turborepo")}} - outputFileTracingRoot: monorepoRoot, - transpilePackages: ["@repo/database"], -{{/if}} -}; - -export default nextConfig; diff --git a/templates/create/next/package.json.hbs b/templates/create/next/package.json.hbs index 9cdd85b..2a5336e 100644 --- a/templates/create/next/package.json.hbs +++ b/templates/create/next/package.json.hbs @@ -1,24 +1,18 @@ { - "name": "{{#if (eq template "turborepo")}}@repo/web{{else}}{{projectName}}{{/if}}", + "name": "{{projectName}}", "version": "0.1.0", "private": true, - {{#unless (eq template "turborepo")}} {{#if (packageManagerManifestValue packageManager)}} "packageManager": "{{packageManagerManifestValue packageManager}}", {{/if}} - {{/unless}} "type": "module", "scripts": { "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint", - "typecheck": "tsc --noEmit" + "lint": "eslint" }, "dependencies": { - {{#if (eq template "turborepo")}} - "@repo/database": "{{#if (eq packageManager "npm")}}*{{else}}workspace:*{{/if}}", - {{/if}} "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3" diff --git a/templates/create/next/src/app/page.tsx.hbs b/templates/create/next/src/app/page.tsx.hbs index 07015f9..eaf1828 100644 --- a/templates/create/next/src/app/page.tsx.hbs +++ b/templates/create/next/src/app/page.tsx.hbs @@ -2,7 +2,7 @@ export const dynamic = "force-dynamic"; export default async function Home() { - const { listUsers } = await import("{{#if (eq template "turborepo")}}@repo/database{{else}}../prisma/users{{/if}}"); + const { listUsers } = await import("../prisma/users"); const formatter = new Intl.DateTimeFormat("en", { dateStyle: "medium", timeStyle: "short", @@ -12,12 +12,12 @@ export default async function Home() { return (
-

{{#if (eq template "turborepo")}}Turborepo + {{/if}}Next.js + Prisma 8

+

Next.js + Prisma 8

Users from your database, loaded on the server.

- This page reads from {{#if (eq template "turborepo")}}apps/web/{{/if}}src/app/page.tsx using the Prisma 8 helper in{" "} - {{#if (eq template "turborepo")}}packages/database/src/users.ts{{else}}src/prisma/users.ts{{/if}}. + This page reads from src/app/page.tsx using the Prisma 8 helper in{" "} + src/prisma/users.ts.

diff --git a/templates/create/turborepo/.gitignore b/templates/create/turborepo/.gitignore index 18e47e2..db4bf34 100644 --- a/templates/create/turborepo/.gitignore +++ b/templates/create/turborepo/.gitignore @@ -1,7 +1,6 @@ node_modules .turbo -apps/web/.next -apps/web/out +dist coverage .env .env.* diff --git a/templates/create/turborepo/apps/server/package.json b/templates/create/turborepo/apps/server/package.json new file mode 100644 index 0000000..f603390 --- /dev/null +++ b/templates/create/turborepo/apps/server/package.json @@ -0,0 +1,15 @@ +{ + "name": "@repo/server", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsdown", + "start": "node dist/server.mjs", + "typecheck": "tsc --noEmit" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } +} diff --git a/templates/create/turborepo/apps/server/src/index.ts b/templates/create/turborepo/apps/server/src/index.ts new file mode 100644 index 0000000..f666206 --- /dev/null +++ b/templates/create/turborepo/apps/server/src/index.ts @@ -0,0 +1,31 @@ +import { createServer } from "node:http"; + +import { listUsers } from "@repo/database"; + +const port = Number(process.env.PORT ?? 3000); + +createServer(async (request, response) => { + if (request.url === "/") { + response.writeHead(200, { "content-type": "text/plain" }); + response.end("Hello World!"); + return; + } + + if (request.url !== "/users") { + response.writeHead(404, { "content-type": "text/plain" }); + response.end("Not found"); + return; + } + + try { + const users = await listUsers(); + response.writeHead(200, { "content-type": "application/json" }); + response.end(JSON.stringify({ users })); + } catch (error) { + console.error("Failed to query users:", error); + response.writeHead(500, { "content-type": "application/json" }); + response.end(JSON.stringify({ error: "Could not query users yet." })); + } +}).listen(port, "0.0.0.0", () => { + console.log(`Server running at http://localhost:${port}`); +}); diff --git a/templates/create/turborepo/apps/server/tsconfig.json b/templates/create/turborepo/apps/server/tsconfig.json new file mode 100644 index 0000000..3b5cc40 --- /dev/null +++ b/templates/create/turborepo/apps/server/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts", "tsdown.config.ts"] +} diff --git a/templates/create/turborepo/package.json.hbs b/templates/create/turborepo/package.json.hbs index 8f63c5a..fdc3c3d 100644 --- a/templates/create/turborepo/package.json.hbs +++ b/templates/create/turborepo/package.json.hbs @@ -13,9 +13,8 @@ "scripts": { "build": "turbo run build", "dev": "turbo run dev --parallel", - "lint": "turbo run lint", "start": "turbo run start --parallel", - "typecheck": "turbo run typecheck" + "typecheck": "tsc --noEmit && turbo run typecheck" }, "devDependencies": {} } diff --git a/templates/create/turborepo/turbo.json b/templates/create/turborepo/turbo.json index 4a8a491..309a197 100644 --- a/templates/create/turborepo/turbo.json +++ b/templates/create/turborepo/turbo.json @@ -1,17 +1,16 @@ { "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["service.ts", "packages/database/src/**"], + "globalPassThroughEnv": ["PORT", "DATABASE_URL", "MONGODB_URL"], "tasks": { "build": { "dependsOn": ["^build"], - "outputs": [".next/**", "!.next/cache/**", "dist/**"] + "outputs": ["dist/**"] }, "dev": { "cache": false, "persistent": true }, - "lint": { - "dependsOn": ["^lint"] - }, "start": { "cache": false, "dependsOn": ["build"], diff --git a/tests/e2e/create-prisma.e2e.test.ts b/tests/e2e/create-prisma.e2e.test.ts index 728f6cc..0ed040a 100644 --- a/tests/e2e/create-prisma.e2e.test.ts +++ b/tests/e2e/create-prisma.e2e.test.ts @@ -140,10 +140,12 @@ async function verifyBuiltServer(projectDir: string, port: number, expectedStatu new Response(process.stderr).text(), ]); let failure: unknown; + let body = ""; try { const response = await fetchUntilResponding(`http://127.0.0.1:${port}`, Date.now() + 10_000); expect(response.status).toBe(expectedStatus); + body = await response.text(); } catch (error) { failure = error; } finally { @@ -157,9 +159,10 @@ async function verifyBuiltServer(projectDir: string, port: number, expectedStatu cause: failure, }); } + return body; } -async function verifyComposerDev(projectDir: string) { +async function verifyComposerDev(projectDir: string, usersPath = "") { const process = Bun.spawn({ cmd: ["bun", "run", "dev:composer"], cwd: projectDir, @@ -200,7 +203,7 @@ async function verifyComposerDev(projectDir: string) { // The endpoint frame can precede the app process binding its port, so // retry connection refusals until the deadline. - const response = await fetchUntilReady(appUrl, deadline); + const response = await fetchUntilReady(`${appUrl}${usersPath}`, deadline); expect(response.status).toBe(200); const body = (await response.json()) as { users: Array<{ name: string }> }; expect(body.users.map((user) => user.name)).toEqual(["Alice", "Bob", "Carol"]); @@ -624,6 +627,13 @@ describe("create-prisma e2e", () => { await runCommand(projectDir, ["bun", "run", "build"]); await runCommand(projectDir, ["bunx", "tsc", "--noEmit"]); + if (template === "turborepo") { + await runCommand(projectDir, ["bun", "run", "typecheck"]); + const serverDir = path.join(projectDir, "apps/server"); + expect(await readdir(path.join(serverDir, "dist"))).toEqual(["server.mjs"]); + expect(await verifyBuiltServer(serverDir, 46_150, 200)).toBe("Hello World!"); + if (process.platform !== "win32") await verifyComposerDev(projectDir, "/users"); + } }, TEST_TIMEOUT, ); diff --git a/tests/install.test.ts b/tests/install.test.ts index eac4b4e..c321fa6 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -344,13 +344,13 @@ describe("generated templates", () => { await writePrismaDependencies(provider, packageManager, authoring, projectDir, { template, }); - const web = await readPackageJson(path.join(projectDir, "apps/web")); + const server = await readPackageJson(path.join(projectDir, "apps/server")); const database = await readPackageJson(path.join(projectDir, "packages/database")); const root = await readPackageJson(projectDir); expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); expect(packageJson.devDependencies?.turbo).toBe(dependencyVersionMap.turbo); - expect(web.name).toBe("@repo/web"); - expect(web.dependencies?.["@repo/database"]).toBe( + expect(server.name).toBe("@repo/server"); + expect(server.dependencies?.["@repo/database"]).toBe( packageManager === "npm" ? "*" : "workspace:*", ); expect(database.name).toBe("@repo/database"); @@ -377,7 +377,21 @@ describe("generated templates", () => { expect(database.scripts).toEqual({ typecheck: "tsc --noEmit --project tsconfig.json", }); - expect(serviceSource).toContain('appDir: "./apps/web"'); + expect(serviceSource).toContain('entry: "./apps/server/dist/server.mjs"'); + expect(serviceSource).not.toContain("nextjs"); + expect(server.scripts?.build).toBe("tsdown"); + expect(server.devDependencies?.tsdown).toBe(dependencyVersionMap.tsdown); + expect(await pathExists(path.join(projectDir, "apps/web"))).toBe(false); + for (const manifest of [root, server, database]) { + expect(manifest.dependencies?.next).toBeUndefined(); + expect(manifest.dependencies?.react).toBeUndefined(); + } + expect( + await readFile(path.join(projectDir, "apps/server/tsdown.config.ts"), "utf8"), + ).toContain('entry: { server: "src/index.ts" }'); + expect( + await readFile(path.join(projectDir, "apps/server/src/index.ts"), "utf8"), + ).toContain('response.end("Hello World!")'); expect(dbSource).toContain('import service from "../../../service.ts"'); expect(await pathExists(path.join(projectDir, "src/prisma"))).toBe(false); } @@ -474,7 +488,7 @@ describe("generated templates", () => { if (packageManager === "pnpm") { expect(packageJson.pnpm).toBeUndefined(); const frameworkBuildAllowances = - template === "next" || template === "turborepo" + template === "next" ? [" sharp: true", " unrs-resolver: true"] : template === "astro" ? [" sharp: true"] From fc125bde653a9aa483a26d608640d81a020c7042 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 15 Sep 2026 13:32:12 +0530 Subject: [PATCH 7/7] fix: use persistent Turbo tasks without deprecated parallel flag Signed-off-by: Aman Varshney --- templates/create/turborepo/package.json.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/create/turborepo/package.json.hbs b/templates/create/turborepo/package.json.hbs index fdc3c3d..459a7b8 100644 --- a/templates/create/turborepo/package.json.hbs +++ b/templates/create/turborepo/package.json.hbs @@ -12,8 +12,8 @@ ], "scripts": { "build": "turbo run build", - "dev": "turbo run dev --parallel", - "start": "turbo run start --parallel", + "dev": "turbo run dev", + "start": "turbo run start", "typecheck": "tsc --noEmit && turbo run typecheck" }, "devDependencies": {}