Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { createCreatePrismaCli } from "./index";

createCreatePrismaCli().run();
await createCreatePrismaCli().run({
formatError: (error: unknown) => (error instanceof Error ? error.message : String(error)),
});

if (process.exitCode && process.exitCode !== 0) {
process.exit(process.exitCode);
}
50 changes: 24 additions & 26 deletions src/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,24 +223,16 @@ export async function runCreateCommand(rawInput: CreateCommandInput = {}): Promi
failureStage = "unknown";
const executionResult = await executeCreateContext(context);
if (!executionResult.ok) {
if (executionResult.error) {
cancel(
`Create command failed: ${
executionResult.error instanceof Error
? executionResult.error.message
: String(executionResult.error)
}`,
);
}

await trackCreateFailed({
input,
context,
durationMs: Date.now() - startedAt,
error: executionResult.error,
stage: executionResult.stage,
});
return;
failureStage = executionResult.stage;
const error =
executionResult.error instanceof Error
? executionResult.error
: new Error(
executionResult.error === undefined
? `Create command failed during ${executionResult.stage}`
: String(executionResult.error),
);
throw error;
}

await trackCreateCompleted({
Expand All @@ -249,14 +241,20 @@ export async function runCreateCommand(rawInput: CreateCommandInput = {}): Promi
durationMs: Date.now() - startedAt,
});
} catch (error) {
cancel(`Create command failed: ${error instanceof Error ? error.message : String(error)}`);
await trackCreateFailed({
input,
context,
durationMs: Date.now() - startedAt,
error,
stage: failureStage,
});
const commandError = error instanceof Error ? error : new Error(String(error));
cancel(`Create command failed: ${commandError.message}`);
try {
await trackCreateFailed({
input,
context,
durationMs: Date.now() - startedAt,
error: commandError,
stage: failureStage,
});
} catch {
// Telemetry is best-effort and must not hide the original command error.
}
throw commandError;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

Expand Down
5 changes: 5 additions & 0 deletions src/tasks/deploy-to-compute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ export async function collectComputeDeployContext(
},
): Promise<ComputeDeployContext | null | undefined> {
if (!isComputeDeployableTemplate(options.template)) {
if (input.deploy === true) {
throw createExplicitDeployError(
`${options.template} is not supported by prisma app deploy yet`,
);
}
return null;
}

Expand Down
8 changes: 8 additions & 0 deletions src/tasks/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,16 @@ export async function installProjectDependencies(
): Promise<void> {
const verbose = options.verbose === true;
const installCommand = getInstallArgs(packageManager);
const env =
packageManager === "yarn"
? {
...process.env,
YARN_ENABLE_IMMUTABLE_INSTALLS: "false",
}
: undefined;
await execa(installCommand.command, installCommand.args, {
cwd: projectDir,
env,
stdio: verbose ? "inherit" : "pipe",
});
}
12 changes: 4 additions & 8 deletions src/tasks/setup-prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { execa } from "execa";
import fs from "fs-extra";
import path from "node:path";

import { escapeRegExp } from "../utils/regexp";
import { installProjectDependencies, writePrismaDependencies } from "./install";
import {
getCreateDbCommand,
Expand All @@ -21,7 +22,7 @@ import {
detectPackageManager,
getInstallCommand,
getPrismaCliArgs,
getPrismaCliCommand,
getRunScriptArgs,
getRunScriptCommand,
} from "../utils/package-manager";

Expand Down Expand Up @@ -376,11 +377,6 @@ function getDefaultDatabaseUrl(provider: DatabaseProvider): string {
}
}

// Escape regex metacharacters before interpolating dynamic values into RegExp.
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function escapeEnvValue(value: string): string {
if (/[\r\n]/.test(value)) {
throw new Error("Environment variable values must be single-line.");
Expand Down Expand Up @@ -660,15 +656,15 @@ async function generatePrismaClientForContext(
};
}

const generateCommand = getPrismaCliCommand(context.packageManager, ["generate"]);
const generateCommand = getRunScriptCommand(context.packageManager, "db:generate");
if (context.verbose) {
log.step(`Running ${generateCommand}`);
}

const generateSpinner = context.verbose ? undefined : spinner();
generateSpinner?.start("Generating Prisma Client...");
try {
const generateArgs = getPrismaCliArgs(context.packageManager, ["generate"]);
const generateArgs = getRunScriptArgs(context.packageManager, "db:generate");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
await execa(generateArgs.command, generateArgs.args, {
cwd: prismaProjectDir,
stdio: context.verbose ? "inherit" : "pipe",
Expand Down
66 changes: 66 additions & 0 deletions src/templates/render-create-template.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import fs from "fs-extra";
import path from "node:path";

import type { CreateTemplate, DatabaseProvider, PackageManager, SchemaPreset } from "../types";
import { escapeRegExp } from "../utils/regexp";
import { renderTemplateTree, resolveTemplatesDir } from "./shared";

type CreateTemplateContext = {
Expand Down Expand Up @@ -29,6 +33,65 @@ function createTemplateContext(
};
}

const pnpmAllowedBuilds = [
"@prisma/engines",
"@parcel/watcher",
"esbuild",
"prisma",
"sharp",
"unrs-resolver",
] as const;

function renderPnpmAllowBuildLine(packageName: string): string {
const key = packageName.startsWith("@") ? JSON.stringify(packageName) : packageName;
return ` ${key}: true`;
}

function renderPnpmAllowBuilds(): string {
return ["allowBuilds:", ...pnpmAllowedBuilds.map(renderPnpmAllowBuildLine)].join("\n");
}

function hasPnpmAllowBuild(content: string, packageName: string): boolean {
const key = escapeRegExp(packageName);
return new RegExp(`^\\s*["']?${key}["']?\\s*:\\s*true\\s*$`, "m").test(content);
}

function mergePnpmAllowBuilds(content: string): string {
const missingBuilds = pnpmAllowedBuilds.filter(
(packageName) => !hasPnpmAllowBuild(content, packageName),
);
if (missingBuilds.length === 0) {
return content;
}

const missingLines = missingBuilds.map(renderPnpmAllowBuildLine);
const trimmedContent = content.trimEnd();
const lines = trimmedContent.length > 0 ? trimmedContent.split("\n") : [];
const allowBuildsIndex = lines.findIndex((line) => /^allowBuilds:\s*$/.test(line));
if (allowBuildsIndex === -1) {
const allowBuilds = ["allowBuilds:", ...missingLines].join("\n");
return trimmedContent.length > 0 ? `${trimmedContent}\n\n${allowBuilds}\n` : `${allowBuilds}\n`;
}

lines.splice(allowBuildsIndex + 1, 0, ...missingLines);
return `${lines.join("\n")}\n`;
}

async function ensurePnpmWorkspaceAllowBuilds(projectDir: string): Promise<void> {
const workspacePath = path.join(projectDir, "pnpm-workspace.yaml");

if (!(await fs.pathExists(workspacePath))) {
await fs.writeFile(workspacePath, `${renderPnpmAllowBuilds()}\n`, "utf8");
return;
}

const existingContent = await fs.readFile(workspacePath, "utf8");
const nextContent = mergePnpmAllowBuilds(existingContent);
if (nextContent !== existingContent) {
await fs.writeFile(workspacePath, nextContent, "utf8");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

export async function scaffoldCreateTemplate(opts: {
projectDir: string;
projectName: string;
Expand All @@ -52,4 +115,7 @@ export async function scaffoldCreateTemplate(opts: {
outputDir: projectDir,
context,
});
if (packageManager === "pnpm") {
await ensurePnpmWorkspaceAllowBuilds(projectDir);
}
}
33 changes: 28 additions & 5 deletions src/utils/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,18 +158,41 @@ export function getInstallCommand(packageManager: PackageManager): string {
}

export function getRunScriptCommand(packageManager: PackageManager, scriptName: string): string {
const { command, args } = getRunScriptArgs(packageManager, scriptName);
return [command, ...args].join(" ");
}

export function getRunScriptArgs(
packageManager: PackageManager,
scriptName: string,
): CommandAndArgs {
switch (packageManager) {
case "deno":
return `deno task ${scriptName}`;
return {
command: "deno",
args: ["task", scriptName],
};
case "bun":
return `bun run ${scriptName}`;
return {
command: "bun",
args: ["run", scriptName],
};
case "pnpm":
return `pnpm run ${scriptName}`;
return {
command: "pnpm",
args: ["run", scriptName],
};
case "yarn":
return `yarn run ${scriptName}`;
return {
command: "yarn",
args: ["run", scriptName],
};
case "npm":
default:
return `npm run ${scriptName}`;
return {
command: "npm",
args: ["run", scriptName],
};
}
}
Comment thread
AmanVarshney01 marked this conversation as resolved.

Expand Down
4 changes: 4 additions & 0 deletions src/utils/regexp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Escape regex metacharacters before interpolating dynamic values into RegExp.
export function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
11 changes: 11 additions & 0 deletions templates/create/astro/astro.config.mjs.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@ export default defineConfig({
output: "server",
adapter: node({ mode: "standalone" }),
server: { host: true },
vite: {
ssr: {
// Bundle SSR dependencies into the server entry. Astro's standalone
// build emits `import { parse, serialize } from "cookie"`, and the Bun
// runtime on Prisma Compute cannot resolve those named exports from
// cookie's CommonJS build at runtime (SyntaxError: Export named 'parse'
// not found). Bundling resolves the imports at build time. Remove once
// the Bun/cookie CJS named-export interop is fixed upstream.
noExternal: true,
},
},
});
3 changes: 3 additions & 0 deletions templates/create/elysia/.yarnrc.yml.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{#if (eq packageManager "yarn")}}
nodeLinker: node-modules
{{/if}}
1 change: 1 addition & 0 deletions templates/create/elysia/package.json.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"openapi-types": "^12.1.3"
},
"devDependencies": {
"@types/node": "^26.0.0",
"typescript": "^6.0.3"
}
}
1 change: 1 addition & 0 deletions templates/create/elysia/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"],
Comment thread
AmanVarshney01 marked this conversation as resolved.
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "."
Expand Down
1 change: 1 addition & 0 deletions templates/create/hono/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"],
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "."
Expand Down
1 change: 1 addition & 0 deletions templates/create/nest/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"],
"forceConsistentCasingInFileNames": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
Expand Down
2 changes: 1 addition & 1 deletion templates/create/tanstack-start/src/routes/index.tsx.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
{{#if (eq schemaPreset "basic")}}
import { createServerFn } from "@tanstack/react-start";
import { prisma } from "../lib/prisma.server";
{{/if}}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Expand Down
2 changes: 1 addition & 1 deletion templates/create/tanstack-start/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@/*": ["./src/*"]
},
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"]
"types": ["vite/client", "node"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "prisma/**/*.ts", "vite.config.ts"]
}
1 change: 1 addition & 0 deletions templates/create/turborepo/apps/api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"],
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "."
Expand Down
8 changes: 8 additions & 0 deletions templates/create/turborepo/packages/db/package.json.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@
"scripts": {
{{#if (eq packageManager "deno")}}
"build": "deno check src/index.ts",
"db:generate": "{{prismaCommand packageManager "generate"}}",
"db:push": "{{prismaCommand packageManager "db push"}}",
"db:migrate": "{{prismaCommand packageManager "migrate dev"}}",
"db:seed": "{{prismaCommand packageManager "db seed"}}",
"typecheck": "deno check src/index.ts"
{{else}}
"build": "tsc",
"db:generate": "{{prismaCommand packageManager "generate"}}",
"db:push": "{{prismaCommand packageManager "db push"}}",
"db:migrate": "{{prismaCommand packageManager "migrate dev"}}",
"db:seed": "{{prismaCommand packageManager "db seed"}}",
"typecheck": "tsc --noEmit"
{{/if}}
},
Expand Down
1 change: 1 addition & 0 deletions templates/create/turborepo/packages/db/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"],
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "."
Expand Down
Loading