From 701b52e69840e50244215671c261dcfdc150a5b5 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Fri, 21 Aug 2026 21:03:08 +0530 Subject: [PATCH 1/2] feat: support Deno projects --- README.md | 18 ++-- src/commands/create.ts | 1 + src/constants/dependencies.ts | 4 + src/tasks/install.ts | 36 +++++++- src/tasks/setup-prisma.ts | 84 +++++++++++++----- src/types.ts | 2 +- src/utils/package-manager.ts | 63 ++++++++++++++ templates/create/_shared/README.md.hbs | 33 +++++++ templates/create/_shared/deno.json.hbs | 5 ++ templates/create/_shared/module.ts.hbs | 2 + .../_shared/prisma-composer.config.ts.hbs | 2 + templates/create/_shared/prisma.config.ts.hbs | 2 + templates/create/_shared/service.ts.hbs | 2 + .../create/_shared/src/prisma/composer.ts.hbs | 2 + templates/create/_shared/src/prisma/db.ts.hbs | 12 +++ templates/create/minimal/package.json.hbs | 6 ++ templates/create/minimal/src/index.ts.hbs | 4 +- tests/e2e/create-prisma.e2e.test.ts | 87 +++++++++++++++++++ tests/install.test.ts | 62 +++++++++++-- tests/setup-prisma.test.ts | 38 ++++++++ 20 files changed, 430 insertions(+), 35 deletions(-) create mode 100644 templates/create/_shared/deno.json.hbs diff --git a/README.md b/README.md index 2a42ae1..2c955bf 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ Create a Prisma 8 app with Prisma Composer built in. Use your package manager: ```bash -npx create-prisma@next my-app -pnpm dlx create-prisma@next my-app -yarn dlx create-prisma@next my-app -bunx create-prisma@next my-app +npx create-prisma@latest my-app +pnpm dlx create-prisma@latest my-app +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. @@ -41,13 +41,21 @@ workspace. Choosing another workspace also updates the Prisma CLI's active works PostgreSQL and MongoDB are supported with PSL or TypeScript contract authoring. npm, pnpm, Yarn, and Bun are supported. +Deno is supported for local minimal PostgreSQL apps: + +```bash +deno run -A npm:create-prisma@latest my-deno-app --template minimal --provider postgres --package-manager deno --no-deploy +``` + +Prisma Compute does not support Deno deployments yet. + ## Options - positional project name or `--name` - `--template` - `--provider postgres|postgresql|mongo|mongodb` - `--authoring psl|typescript` -- `--package-manager npm|pnpm|yarn|bun` +- `--package-manager npm|pnpm|yarn|bun|deno` - `--deploy` / `--no-deploy` - `--workspace ` - `--yes` diff --git a/src/commands/create.ts b/src/commands/create.ts index 5d96b69..1bc479c 100644 --- a/src/commands/create.ts +++ b/src/commands/create.ts @@ -291,6 +291,7 @@ async function collectCreateContext( const prismaSetupContext = await collectPrismaSetupContext(input, { projectDir: targetDirectory, + template, }); if (!prismaSetupContext) { return; diff --git a/src/constants/dependencies.ts b/src/constants/dependencies.ts index 5f3b11a..5d995b1 100644 --- a/src/constants/dependencies.ts +++ b/src/constants/dependencies.ts @@ -12,6 +12,7 @@ export const dependencyVersionMap = { "@types/node": "^25.6.2", alchemy: "2.0.0-beta.67", arktype: "^2.2.3", + dotenv: "^17.4.2", esbuild: "^0.28.1", effect: "4.0.0-beta.103", mongodb: "^7.1.0", @@ -22,6 +23,9 @@ export const dependencyVersionMap = { } as const; export const PRISMA_PLATFORM_CLI_PACKAGE = "prisma@next"; +// The consolidated CLI currently imports Node-only credential storage when Deno starts it. +// Keep Deno on Prisma 8's ORM-only CLI entrypoint until that upstream path is Deno-compatible. +export const PRISMA_DENO_CLI_PACKAGE = "prisma-next"; export type AvailableDependency = keyof typeof dependencyVersionMap; diff --git a/src/tasks/install.ts b/src/tasks/install.ts index 47a1dd5..ebf7a80 100644 --- a/src/tasks/install.ts +++ b/src/tasks/install.ts @@ -5,6 +5,7 @@ import path from "node:path"; import { getCreateTemplateDependencies, getDependencyVersion, + PRISMA_DENO_CLI_PACKAGE, PRISMA_PLATFORM_CLI_PACKAGE, } from "../constants/dependencies"; import { getDbPackages } from "../constants/db-packages"; @@ -16,6 +17,27 @@ import { } from "../utils/package-manager"; function getPrismaScriptMap(packageManager: PackageManager): Record { + if (packageManager === "deno") { + const prismaCommand = (needsDatabase: boolean, ...args: string[]) => + [ + "deno run -A", + ...(needsDatabase ? ["--env-file=.env"] : []), + `npm:${PRISMA_DENO_CLI_PACKAGE}`, + ...args, + ].join(" "); + + return { + "contract:emit": prismaCommand(false, "contract", "emit"), + "db:init": prismaCommand(true, "db", "init"), + "db:update": prismaCommand(true, "db", "update"), + "db:verify": prismaCommand(true, "db", "verify"), + "migration:plan": prismaCommand(true, "migration", "plan"), + migrate: prismaCommand(true, "migrate"), + "migration:status": prismaCommand(true, "migration", "status"), + "migration:show": prismaCommand(true, "migration", "show"), + }; + } + const prismaCommand = (...args: string[]) => getPackageExecutionCommand(packageManager, [PRISMA_PLATFORM_CLI_PACKAGE, ...args]); @@ -32,6 +54,10 @@ function getPrismaScriptMap(packageManager: PackageManager): Record { + if (packageManager === "deno") { + return {}; + } + const composerCommand = (subcommand: string, extraArgs: string[] = []) => getPackageExecutionCommand(packageManager, [ PRISMA_PLATFORM_CLI_PACKAGE, @@ -131,10 +157,14 @@ export async function writePrismaDependencies( ): Promise { const dependencies = [getDbPackages(provider)]; if (provider === "mongo") dependencies.push("arktype", "mongodb"); + if (packageManager === "deno") dependencies.push("dotenv"); + + const devDependencies = + packageManager === "deno" ? ["@types/node"] : ["@prisma/cli-engine", "@types/node"]; await addPackageDependency({ dependencies, - devDependencies: ["@prisma/cli-engine", "@types/node"], + devDependencies, scripts: getPrismaScriptMap(packageManager), projectDir, }); @@ -147,6 +177,10 @@ export async function writeCreateTemplateDependencies(opts: { }): Promise { const { template, packageManager, projectDir = process.cwd() } = opts; + if (packageManager === "deno") { + return; + } + for (const target of getCreateTemplateDependencies(template, packageManager)) { await addPackageDependency({ dependencies: target.dependencies, diff --git a/src/tasks/setup-prisma.ts b/src/tasks/setup-prisma.ts index bc9556b..712a845 100644 --- a/src/tasks/setup-prisma.ts +++ b/src/tasks/setup-prisma.ts @@ -3,12 +3,13 @@ import { execa } from "execa"; import fs from "fs-extra"; import path from "node:path"; -import { PRISMA_PLATFORM_CLI_PACKAGE } from "../constants/dependencies"; +import { PRISMA_DENO_CLI_PACKAGE, PRISMA_PLATFORM_CLI_PACKAGE } from "../constants/dependencies"; import { scaffoldCreateSharedTemplates } from "../templates/render-create-template"; import { AuthoringStyleSchema, DatabaseProviderSchema, PackageManagerSchema, + packageManagers, type AuthoringStyle, type CreateTemplate, type DatabaseProvider, @@ -91,6 +92,7 @@ function getPackageManagerHint(option: PackageManager, detected: PackageManager) pnpm: "Fast, disk-efficient package manager", yarn: "Yarn package manager", bun: "Fast runtime and package manager", + deno: "Deno runtime (minimal PostgreSQL apps)", } satisfies Record; return option === detected ? `Detected; ${hints[option]}` : hints[option]; } @@ -101,7 +103,7 @@ async function promptForPackageManager( const packageManager = await select({ message: "Choose package manager", initialValue: detected, - options: (["npm", "pnpm", "yarn", "bun"] as const).map((value) => ({ + options: packageManagers.map((value) => ({ value, label: value, hint: getPackageManagerHint(value, detected), @@ -128,7 +130,7 @@ async function promptForDeployment(): Promise { export async function collectPrismaSetupContext( input: PrismaSetupCommandInput, - options: { projectDir?: string } = {}, + options: { projectDir?: string; template?: CreateTemplate } = {}, ): Promise { const projectDir = path.resolve(options.projectDir ?? process.cwd()); const useDefaults = input.yes === true; @@ -147,7 +149,20 @@ export async function collectPrismaSetupContext( (useDefaults ? detectedPackageManager : await promptForPackageManager(detectedPackageManager)); if (!packageManager) return; - const shouldDeploy = input.deploy ?? (useDefaults ? false : await promptForDeployment()); + if (packageManager === "deno" && databaseProvider !== "postgres") { + throw new Error("Deno support currently requires PostgreSQL."); + } + if (packageManager === "deno" && options.template && options.template !== "minimal") { + throw new Error("Deno support currently requires the minimal template."); + } + if (packageManager === "deno" && input.deploy === true) { + throw new Error("Prisma Compute does not support Deno deployments yet. Use --no-deploy."); + } + + const shouldDeploy = + packageManager === "deno" + ? false + : (input.deploy ?? (useDefaults ? false : await promptForDeployment())); if (shouldDeploy === undefined) return; return { @@ -179,24 +194,41 @@ function getInitTarget(provider: DatabaseProvider): "postgres" | "mongodb" { } function getPrismaCliInvocation(packageManager: PackageManager, args: string[]) { - return getPackageExecutionArgs(packageManager, [PRISMA_PLATFORM_CLI_PACKAGE, ...args]); + const packageName = + packageManager === "deno" ? PRISMA_DENO_CLI_PACKAGE : PRISMA_PLATFORM_CLI_PACKAGE; + return getPackageExecutionArgs(packageManager, [packageName, ...args]); } async function runPrismaInit(context: PrismaSetupContext, projectDir: string): Promise { - const args = [ - "orm", - "init", - "--yes", - "--no-interactive", - "--target", - getInitTarget(context.databaseProvider), - "--authoring", - context.authoring, - "--schema-path", - getContractPath(context.authoring), - "--skip-install", - "--skip-skills", - ]; + const args = + context.packageManager === "deno" + ? [ + "init", + "--yes", + "--no-interactive", + "--target", + getInitTarget(context.databaseProvider), + "--authoring", + context.authoring, + "--schema-path", + getContractPath(context.authoring), + "--no-install", + "--no-skill", + ] + : [ + "orm", + "init", + "--yes", + "--no-interactive", + "--target", + getInitTarget(context.databaseProvider), + "--authoring", + context.authoring, + "--schema-path", + getContractPath(context.authoring), + "--skip-install", + "--skip-skills", + ]; const invocation = getPrismaCliInvocation(context.packageManager, args); if (context.verbose) log.step(`Running ${[invocation.command, ...invocation.args].join(" ")}`); await execa(invocation.command, invocation.args, { @@ -204,6 +236,7 @@ async function runPrismaInit(context: PrismaSetupContext, projectDir: string): P stdio: context.verbose ? "inherit" : "pipe", env: { ...process.env, CI: "1" }, }); + if (context.packageManager === "deno") await fs.remove(path.join(projectDir, "prisma-next.md")); } async function ensureGitignoreEntry(projectDir: string, entry: string): Promise { @@ -312,10 +345,19 @@ function buildNextSteps(context: PrismaSetupContext, options: PrismaSetupRunOpti } if (options.includeDevNextStep) { nextSteps.push({ - command: getRunScriptCommand(context.packageManager, "dev:composer"), - description: "Build and start the app with Prisma Composer locally.", + command: getRunScriptCommand( + context.packageManager, + context.packageManager === "deno" ? "dev" : "dev:composer", + ), + description: + context.packageManager === "deno" + ? "Start the Deno app after setting DATABASE_URL in .env." + : "Build and start the app with Prisma Composer locally.", }); } + if (context.packageManager === "deno") { + return nextSteps; + } nextSteps.push({ command: getRunScriptCommand(context.packageManager, "deploy"), description: "Build and deploy the app with Prisma Composer.", diff --git a/src/types.ts b/src/types.ts index c259164..cfef788 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,7 +3,7 @@ import { z } from "zod"; export const databaseProviders = ["postgres", "mongo"] as const; export const databaseProviderInputs = ["postgres", "postgresql", "mongo", "mongodb"] as const; -export const packageManagers = ["npm", "pnpm", "yarn", "bun"] as const; +export const packageManagers = ["npm", "pnpm", "yarn", "bun", "deno"] as const; export const authoringStyles = ["psl", "typescript"] as const; export const createTemplates = [ "minimal", diff --git a/src/utils/package-manager.ts b/src/utils/package-manager.ts index 4f60d70..0ac51a7 100644 --- a/src/utils/package-manager.ts +++ b/src/utils/package-manager.ts @@ -34,6 +34,10 @@ function parseUserAgent(userAgent: string | undefined): PackageManager | null { return "bun"; } + if (userAgent?.startsWith("deno")) { + return "deno"; + } + if (userAgent?.startsWith("npm")) { return "npm"; } @@ -61,6 +65,16 @@ async function detectFromPackageJson(projectDir: string): Promise { + for (const configFile of ["deno.json", "deno.jsonc"]) { + if (await fs.pathExists(path.join(projectDir, configFile))) { + return "deno"; + } + } + + return null; +} + async function detectFromLockfile(projectDir: string): Promise { const lockfileChecks: Array<{ manager: PackageManager; lockfile: string }> = [ { manager: "pnpm", lockfile: "pnpm-lock.yaml" }, @@ -69,6 +83,7 @@ async function detectFromLockfile(projectDir: string): Promise { }); {{/if}} }); +{{/unless}} diff --git a/templates/create/_shared/prisma-composer.config.ts.hbs b/templates/create/_shared/prisma-composer.config.ts.hbs index e2561e2..e4fe7cc 100644 --- a/templates/create/_shared/prisma-composer.config.ts.hbs +++ b/templates/create/_shared/prisma-composer.config.ts.hbs @@ -1,3 +1,4 @@ +{{#unless (eq packageManager "deno")}} import { defineConfig } from "@prisma/composer/config"; import { nodeBuild } from "@prisma/composer/node/control"; {{#if (eq template "next")}} @@ -9,3 +10,4 @@ export default defineConfig({ extensions: [prismaCloud(), nodeBuild(){{#if (eq template "next")}}, nextjsBuild(){{/if}}], state: prismaState(), }); +{{/unless}} diff --git a/templates/create/_shared/prisma.config.ts.hbs b/templates/create/_shared/prisma.config.ts.hbs index 4fdf962..520466e 100644 --- a/templates/create/_shared/prisma.config.ts.hbs +++ b/templates/create/_shared/prisma.config.ts.hbs @@ -1,3 +1,4 @@ +{{#unless (eq packageManager "deno")}} import { definePrismaConfig } from "@prisma/cli-engine"; import { defineConfig as ormConfig } from "@prisma/orm-{{#if (eq provider "postgres")}}postgres{{else}}mongo{{/if}}/config"; @@ -15,3 +16,4 @@ export default definePrismaConfig({ configPath: "./prisma-composer.config.ts", }, }); +{{/unless}} diff --git a/templates/create/_shared/service.ts.hbs b/templates/create/_shared/service.ts.hbs index 9265e86..8af3e00 100644 --- a/templates/create/_shared/service.ts.hbs +++ b/templates/create/_shared/service.ts.hbs @@ -1,3 +1,4 @@ +{{#unless (eq packageManager "deno")}} {{#if (eq template "next")}} import nextjs from "@prisma/composer/nextjs"; {{else}} @@ -40,3 +41,4 @@ export default compute({ build: node({ module: import.meta.url, entry: "./dist/server.mjs" }), {{/if}} }); +{{/unless}} diff --git a/templates/create/_shared/src/prisma/composer.ts.hbs b/templates/create/_shared/src/prisma/composer.ts.hbs index 71ee7fe..4dcaebb 100644 --- a/templates/create/_shared/src/prisma/composer.ts.hbs +++ b/templates/create/_shared/src/prisma/composer.ts.hbs @@ -1,3 +1,4 @@ +{{#unless (eq packageManager "deno")}} {{#if (eq provider "postgres")}} import { pnContract } from "@prisma/composer-prisma-cloud/prisma-next"; @@ -6,3 +7,4 @@ import contractJson from "./{{#if (eq authoring "typescript")}}generated/{{/if}} export const appContract = pnContract(contractJson); {{/if}} +{{/unless}} diff --git a/templates/create/_shared/src/prisma/db.ts.hbs b/templates/create/_shared/src/prisma/db.ts.hbs index eb9ccf3..bd47963 100644 --- a/templates/create/_shared/src/prisma/db.ts.hbs +++ b/templates/create/_shared/src/prisma/db.ts.hbs @@ -1,6 +1,17 @@ {{#if (eq provider "postgres")}} import postgres from "@prisma/orm-postgres/runtime"; +{{#if (eq packageManager "deno")}} +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" }; + +const databaseUrl = Deno.env.get("DATABASE_URL"); +if (!databaseUrl) { + throw new Error("DATABASE_URL is not set. Add it to .env before starting the app."); +} + +export const db = postgres({ contractJson, url: databaseUrl }); +{{else}} import service from "../../service.ts"; 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" }; @@ -18,6 +29,7 @@ export const db = (process.env.DATABASE_URL ? postgres({ contractJson, url: process.env.DATABASE_URL }) : postgres({ contractJson })); +{{/if}} {{else}} import mongo from "@prisma/orm-mongo/runtime"; diff --git a/templates/create/minimal/package.json.hbs b/templates/create/minimal/package.json.hbs index d801711..05009e8 100644 --- a/templates/create/minimal/package.json.hbs +++ b/templates/create/minimal/package.json.hbs @@ -6,9 +6,15 @@ {{/if}} "type": "module", "scripts": { + {{#if (eq packageManager "deno")}} + "dev": "{{runtimeScript packageManager "dev" "src/index.ts" "dist/server.mjs"}}", + "build": "{{runtimeScript packageManager "build" "src/index.ts" "dist/server.mjs"}}", + "start": "{{runtimeScript packageManager "start" "src/index.ts" "dist/server.mjs"}}" + {{else}} "dev": "tsx watch src/index.ts", "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/server.mjs", "start": "node dist/server.mjs" + {{/if}} }, "dependencies": {}, "devDependencies": {} diff --git a/templates/create/minimal/src/index.ts.hbs b/templates/create/minimal/src/index.ts.hbs index fe17f17..2d731e9 100644 --- a/templates/create/minimal/src/index.ts.hbs +++ b/templates/create/minimal/src/index.ts.hbs @@ -1,8 +1,8 @@ import { createServer } from "node:http"; -import { listUsers } from "./prisma/users"; +import { listUsers } from "./prisma/users{{#if (eq packageManager "deno")}}.ts{{/if}}"; -const port = Number(process.env.PORT ?? 3000); +const port = Number({{#if (eq packageManager "deno")}}Deno.env.get("PORT"){{else}}process.env.PORT{{/if}} ?? 3000); createServer(async (_request, response) => { try { diff --git a/tests/e2e/create-prisma.e2e.test.ts b/tests/e2e/create-prisma.e2e.test.ts index fb95aa4..1c2428b 100644 --- a/tests/e2e/create-prisma.e2e.test.ts +++ b/tests/e2e/create-prisma.e2e.test.ts @@ -137,6 +137,45 @@ describe("create-prisma e2e", () => { expect(await pathExists(path.join(rootDir, "failed-app", "package.json"))).toBe(true); }); + test("rejects unsupported Deno combinations with a non-zero exit code", async () => { + const rootDir = await mkdtemp(path.join(tmpdir(), "create-prisma-deno-reject-e2e-")); + tempRoots.push(rootDir); + + const child = Bun.spawn({ + cmd: [ + process.execPath, + path.join(import.meta.dir, "../../src/cli.ts"), + "create", + "unsupported-deno-app", + "--template", + "next", + "--provider", + "postgres", + "--authoring", + "psl", + "--package-manager", + "deno", + "--no-deploy", + "--yes", + ], + cwd: rootDir, + env: { ...Bun.env, CI: "1", CREATE_PRISMA_DISABLE_TELEMETRY: "1" }, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(child.stdout).text(), + new Response(child.stderr).text(), + child.exited, + ]); + expect(exitCode).toBe(1); + expect(`${stdout}\n${stderr}`).toContain( + "Deno support currently requires the minimal template", + ); + expect(await pathExists(path.join(rootDir, "unsupported-deno-app"))).toBe(false); + }); + test( "generates, builds, and runs a Composer-backed Prisma Postgres app", async () => { @@ -223,4 +262,52 @@ describe("create-prisma e2e", () => { }, TEST_TIMEOUT, ); + + test( + "generates and checks a minimal Deno Prisma Postgres app", + async () => { + const rootDir = await mkdtemp(path.join(tmpdir(), "create-prisma-deno-e2e-")); + tempRoots.push(rootDir); + const previousCwd = process.cwd(); + process.chdir(rootDir); + try { + await runCreateCommand({ + name: "deno-app", + template: "minimal", + provider: "postgres", + authoring: "psl", + packageManager: "deno", + deploy: false, + yes: true, + }); + } finally { + process.chdir(previousCwd); + } + + const projectDir = path.join(rootDir, "deno-app"); + const packageJson = JSON.parse( + await readFile(path.join(projectDir, "package.json"), "utf8"), + ) as Record; + const configSource = await readFile(path.join(projectDir, "prisma-next.config.ts"), "utf8"); + const dbSource = await readFile(path.join(projectDir, "src/prisma/db.ts"), "utf8"); + + expect(await pathExists(path.join(projectDir, "deno.json"))).toBe(true); + expect(await pathExists(path.join(projectDir, "src/prisma/contract.json"))).toBe(true); + expect(await pathExists(path.join(projectDir, "src/prisma/contract.d.ts"))).toBe(true); + expect(await pathExists(path.join(projectDir, "prisma-next.md"))).toBe(false); + expect(await pathExists(path.join(projectDir, "module.ts"))).toBe(false); + expect(await pathExists(path.join(projectDir, "service.ts"))).toBe(false); + expect(await pathExists(path.join(projectDir, "prisma.config.ts"))).toBe(false); + expect(configSource).toContain("dotenv/config"); + expect(dbSource).toContain('Deno.env.get("DATABASE_URL")'); + expect(packageJson.scripts).toMatchObject({ + build: "deno check src/index.ts", + dev: "deno run -A --env-file=.env --watch src/index.ts", + }); + expect(packageJson.scripts.deploy).toBeUndefined(); + + await runCommand(projectDir, ["deno", "task", "build"]); + }, + TEST_TIMEOUT, + ); }); diff --git a/tests/install.test.ts b/tests/install.test.ts index 43dd0a1..495b60d 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -3,7 +3,11 @@ import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; -import { dependencyVersionMap, PRISMA_PLATFORM_CLI_PACKAGE } from "../src/constants/dependencies"; +import { + dependencyVersionMap, + PRISMA_DENO_CLI_PACKAGE, + PRISMA_PLATFORM_CLI_PACKAGE, +} from "../src/constants/dependencies"; import { scaffoldCreateTemplate } from "../src/templates/render-create-template"; import { getComposerScriptMap, @@ -75,6 +79,26 @@ describe("writePrismaDependencies", () => { expect(packageJson.scripts?.["db:seed"]).toBeUndefined(); }); }); + + test("writes Deno-native Prisma scripts without Composer dependencies", async () => { + await withPackageJson(async (projectDir) => { + await writePrismaDependencies("postgres", "deno", "psl", projectDir); + const packageJson = await readPackageJson(projectDir); + + expect(packageJson.dependencies).toMatchObject({ + "@prisma/orm-postgres": dependencyVersionMap["@prisma/orm-postgres"], + dotenv: dependencyVersionMap.dotenv, + }); + expect(packageJson.devDependencies).toMatchObject({ + "@types/node": dependencyVersionMap["@types/node"], + }); + expect(packageJson.devDependencies?.["@prisma/cli-engine"]).toBeUndefined(); + expect(packageJson.scripts).toMatchObject({ + "contract:emit": `deno run -A npm:${PRISMA_DENO_CLI_PACKAGE} contract emit`, + "db:init": `deno run -A --env-file=.env npm:${PRISMA_DENO_CLI_PACKAGE} db init`, + }); + }); + }); }); describe("Composer package-manager commands", () => { @@ -91,13 +115,14 @@ describe("Composer package-manager commands", () => { expect(getComposerScriptMap("bun")["composer:deploy"]).toBe( `bunx ${PRISMA_PLATFORM_CLI_PACKAGE} composer deploy module.ts`, ); + expect(getComposerScriptMap("deno")).toEqual({}); }); test("keeps installs package-manager native", () => { for (const packageManager of packageManagers) { expect(getInstallArgs(packageManager)).toEqual({ command: packageManager, - args: ["install"], + args: packageManager === "deno" ? ["install", "--allow-scripts"] : ["install"], }); } }); @@ -109,6 +134,10 @@ describe("generated templates", () => { for (const provider of databaseProviders) { for (const authoring of authoringStyles) { for (const packageManager of packageManagers) { + if (packageManager === "deno" && (template !== "minimal" || provider !== "postgres")) { + continue; + } + const projectDir = await mkdtemp(path.join(tmpdir(), "create-prisma-matrix-")); try { await scaffoldCreateTemplate({ @@ -122,8 +151,6 @@ describe("generated templates", () => { await writeCreateTemplateDependencies({ template, packageManager, projectDir }); const packageJson = await readPackageJson(projectDir); - const moduleSource = await readFile(path.join(projectDir, "module.ts"), "utf8"); - const serviceSource = await readFile(path.join(projectDir, "service.ts"), "utf8"); const dbSource = await readFile(path.join(projectDir, "src/prisma/db.ts"), "utf8"); const seedSource = await readFile( path.join(projectDir, "src/prisma/seed.ts"), @@ -133,11 +160,36 @@ describe("generated templates", () => { path.join(projectDir, "src/prisma/users.ts"), "utf8", ); + const tsconfig = await readFile(path.join(projectDir, "tsconfig.json"), "utf8"); + + if (packageManager === "deno") { + expect(await pathExists(path.join(projectDir, "deno.json"))).toBe(true); + expect(await pathExists(path.join(projectDir, "module.ts"))).toBe(false); + expect(await pathExists(path.join(projectDir, "service.ts"))).toBe(false); + expect(await pathExists(path.join(projectDir, "prisma.config.ts"))).toBe(false); + expect(await pathExists(path.join(projectDir, "prisma-composer.config.ts"))).toBe( + false, + ); + expect(packageJson.dependencies?.["@prisma/composer"]).toBeUndefined(); + expect(packageJson.scripts).toMatchObject({ + build: "deno check src/index.ts", + dev: "deno run -A --env-file=.env --watch src/index.ts", + start: "deno run -A --env-file=.env src/index.ts", + }); + expect(packageJson.scripts?.deploy).toBeUndefined(); + expect(dbSource).toContain('Deno.env.get("DATABASE_URL")'); + expect(dbSource).not.toContain('import service from "../../service.ts"'); + expect(seedSource).toContain("await connectDatabase()"); + expect(usersSource).toContain("await seed()"); + continue; + } + + const moduleSource = await readFile(path.join(projectDir, "module.ts"), "utf8"); + const serviceSource = await readFile(path.join(projectDir, "service.ts"), "utf8"); const prismaConfig = await readFile( path.join(projectDir, "prisma.config.ts"), "utf8", ); - const tsconfig = await readFile(path.join(projectDir, "tsconfig.json"), "utf8"); expect(packageJson.scripts?.deploy).toBeDefined(); expect(packageJson.dependencies).toHaveProperty("@prisma/composer"); diff --git a/tests/setup-prisma.test.ts b/tests/setup-prisma.test.ts index 9b1649c..a3022db 100644 --- a/tests/setup-prisma.test.ts +++ b/tests/setup-prisma.test.ts @@ -84,4 +84,42 @@ describe("collectPrismaSetupContext", () => { expect(context?.databaseProvider).toBe("mongo"); }); }); + + test("supports Deno for local minimal PostgreSQL apps", async () => { + await withTempProject(async (projectDir) => { + const context = await collectPrismaSetupContext( + { yes: true, packageManager: "deno", provider: "postgres" }, + { projectDir, template: "minimal" }, + ); + + expect(context).toMatchObject({ + databaseProvider: "postgres", + packageManager: "deno", + shouldDeploy: false, + }); + }); + }); + + test("rejects Deno for unsupported providers, templates, and deployments", async () => { + await withTempProject(async (projectDir) => { + await expect( + collectPrismaSetupContext( + { yes: true, packageManager: "deno", provider: "mongo" }, + { projectDir, template: "minimal" }, + ), + ).rejects.toThrow("Deno support currently requires PostgreSQL."); + await expect( + collectPrismaSetupContext( + { yes: true, packageManager: "deno", provider: "postgres" }, + { projectDir, template: "next" }, + ), + ).rejects.toThrow("Deno support currently requires the minimal template."); + await expect( + collectPrismaSetupContext( + { yes: true, packageManager: "deno", provider: "postgres", deploy: true }, + { projectDir, template: "minimal" }, + ), + ).rejects.toThrow("Prisma Compute does not support Deno deployments yet. Use --no-deploy."); + }); + }); }); From 178f9f6b0293f484fd7b7a8e5c476029962e5272 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Fri, 21 Aug 2026 21:25:27 +0530 Subject: [PATCH 2/2] fix: avoid unrestricted Deno lifecycle scripts --- src/utils/package-manager.ts | 4 ++-- tests/install.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/package-manager.ts b/src/utils/package-manager.ts index 0ac51a7..e8ffe3d 100644 --- a/src/utils/package-manager.ts +++ b/src/utils/package-manager.ts @@ -135,7 +135,7 @@ export function getPackageManagerManifestValue( export function getInstallCommand(packageManager: PackageManager): string { if (packageManager === "deno") { - return "deno install --allow-scripts"; + return "deno install"; } return `${packageManager} install`; @@ -200,7 +200,7 @@ export function getInstallArgs(packageManager: PackageManager): CommandAndArgs { if (packageManager === "deno") { return { command: "deno", - args: ["install", "--allow-scripts"], + args: ["install"], }; } diff --git a/tests/install.test.ts b/tests/install.test.ts index 495b60d..ad74982 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -122,7 +122,7 @@ describe("Composer package-manager commands", () => { for (const packageManager of packageManagers) { expect(getInstallArgs(packageManager)).toEqual({ command: packageManager, - args: packageManager === "deno" ? ["install", "--allow-scripts"] : ["install"], + args: ["install"], }); } });