Skip to content

#1926 - apple 2nd try (use this one) #2374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
50 changes: 50 additions & 0 deletions docs/config/extensions/prismaExtension.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,53 @@ prismaExtension({
migrate: false,
}),
```

### Example Usage (Prisma 6.6.0+)

```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";

export default defineConfig({
project: "<project ref>",
build: {
extensions: [
prismaExtension({
schema: "prisma/schema.prisma",
output: "prisma/generated/client", // required for Prisma 6.6.0+
version: "6.6.0", // optional
}),
],
},
});
```

### Using `prisma.config.ts`

If you have a `prisma.config.ts` file, you can specify `schema` and `output` there instead of in the extension options:

```ts
// prisma.config.ts
export default {
schema: "prisma/schema.prisma",
output: "prisma/generated/client",
};
```

The extension will use these values automatically if not provided in the extension options.

### Migrations

The `migrations` directory must be alongside your `schema.prisma` file. The extension will copy migrations from this location during the build process if `migrate: true` is set.

### Migration Guide: Upgrading to Prisma 6.6.0+

1. Move your `schema.prisma` file into a dedicated directory (e.g., `prisma/schema/`).
2. Place your `migrations` directory alongside `schema.prisma`.
3. Specify the `schema` and `output` options in your `prismaExtension` call or in `prisma.config.ts`.
4. Update any code that previously expected the client in `node_modules` to use the new output path.
5. If you use multiple schemas, repeat the above for each schema.

### Error Handling

If required paths are missing or misconfigured, the extension will throw clear errors to help you fix your setup.
210 changes: 57 additions & 153 deletions packages/build/src/extensions/prisma.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,30 @@
import { BuildManifest, BuildTarget } from "@trigger.dev/core/v3";
import { binaryForRuntime, BuildContext, BuildExtension } from "@trigger.dev/core/v3/build";
import assert from "node:assert";
import { existsSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { cp, readdir } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { dirname, join, resolve, extname } from "node:path";
import { pathToFileURL } from "url";

// Utility to load prisma.config.ts if it exists
async function loadPrismaConfig(workingDir: string): Promise<any | undefined> {
const configPath = resolve(workingDir, "prisma.config.ts");
if (!existsSync(configPath)) return undefined;
// Use dynamic import to load the config (assumes ESM or transpiled TS)
try {
const config = await import(pathToFileURL(configPath).href);
return config.default || config;
} catch (e) {
throw new Error(`Failed to load prisma.config.ts: ${e}`);
}
}

export type PrismaExtensionOptions = {
schema: string;
schema?: string; // Now optional if provided in config
output?: string; // New: explicit output path for Prisma client
migrate?: boolean;
version?: string;
/**
* Adds the `--sql` flag to the `prisma generate` command. This will generate the SQL files for the Prisma schema. Requires the `typedSql preview feature and prisma 5.19.0 or later.
*/
typedSql?: boolean;
/**
* The client generator to use. Set this param to prevent all generators in the prisma schema from being generated.
*
* @example
*
* ### Prisma schema
*
* ```prisma
* generator client {
* provider = "prisma-client-js"
* }
*
* generator typegraphql {
* provider = "typegraphql-prisma"
* output = "./generated/type-graphql"
* }
* ```
*
* ### PrismaExtension
*
* ```ts
* prismaExtension({
* schema: "./prisma/schema.prisma",
* clientGenerator: "client"
* });
* ```
*/
clientGenerator?: string;
directUrlEnvVarName?: string;
};
Expand All @@ -52,10 +37,10 @@ export function prismaExtension(options: PrismaExtensionOptions): PrismaExtensio

export class PrismaExtension implements BuildExtension {
moduleExternals: string[];

public readonly name = "PrismaExtension";

private _resolvedSchemaPath?: string;
private _resolvedOutputPath?: string;
private _prismaConfig?: any;

constructor(private options: PrismaExtensionOptions) {
this.moduleExternals = ["@prisma/client", "@prisma/engines"];
Expand All @@ -73,169 +58,91 @@ export class PrismaExtension implements BuildExtension {
if (context.target === "dev") {
return;
}

// Resolve the path to the prisma schema, relative to the config.directory
this._resolvedSchemaPath = resolve(context.workingDir, this.options.schema);

// Load prisma.config.ts if it exists
this._prismaConfig = await loadPrismaConfig(context.workingDir);
// Determine schema path
const schemaPath =
this.options.schema || this._prismaConfig?.schema || this._prismaConfig?.schemaPath;
if (!schemaPath) {
throw new Error(
`PrismaExtension requires a schema path. Provide it in options or prisma.config.ts (schema or schemaPath).`
);
}
this._resolvedSchemaPath = resolve(context.workingDir, schemaPath);
context.logger.debug(`Resolved the prisma schema to: ${this._resolvedSchemaPath}`);

// Check that the prisma schema exists
if (!existsSync(this._resolvedSchemaPath)) {
throw new Error(
`PrismaExtension could not find the prisma schema at ${this._resolvedSchemaPath}. Make sure the path is correct: ${this.options.schema}, relative to the working dir ${context.workingDir}`
`PrismaExtension could not find the prisma schema at ${this._resolvedSchemaPath}. Make sure the path is correct: ${schemaPath}, relative to the working dir ${context.workingDir}`
);
}
// Determine output path for Prisma client
const outputPath =
this.options.output || this._prismaConfig?.output || this._prismaConfig?.clientOutput;
if (!outputPath) {
throw new Error(
`PrismaExtension requires an output path for the Prisma client. Provide it in options or prisma.config.ts (output or clientOutput).`
);
}
this._resolvedOutputPath = resolve(context.workingDir, outputPath);
context.logger.debug(`Resolved the Prisma client output to: ${this._resolvedOutputPath}`);
}

async onBuildComplete(context: BuildContext, manifest: BuildManifest) {
if (context.target === "dev") {
return;
}

assert(this._resolvedSchemaPath, "Resolved schema path is not set");

assert(this._resolvedOutputPath, "Resolved output path is not set");
context.logger.debug("Looking for @prisma/client in the externals", {
externals: manifest.externals,
});

const prismaExternal = manifest.externals?.find(
(external) => external.name === "@prisma/client"
);

const version = prismaExternal?.version ?? this.options.version;

if (!version) {
throw new Error(
`PrismaExtension could not determine the version of @prisma/client. It's possible that the @prisma/client was not used in the project. If this isn't the case, please provide a version in the PrismaExtension options.`
);
}

context.logger.debug(`PrismaExtension is generating the Prisma client for version ${version}`);

const usingSchemaFolder = dirname(this._resolvedSchemaPath).endsWith("schema");

const commands: string[] = [];

let prismaDir: string | undefined;

const generatorFlags: string[] = [];

if (this.options.clientGenerator) {
generatorFlags.push(`--generator=${this.options.clientGenerator}`);
}

if (this.options.typedSql) {
generatorFlags.push(`--sql`);

const prismaDir = usingSchemaFolder
? dirname(dirname(this._resolvedSchemaPath))
: dirname(this._resolvedSchemaPath);

context.logger.debug(`Using typedSql`);

// Find all the files prisma/sql/*.sql
const sqlFiles = await readdir(join(prismaDir, "sql")).then((files) =>
files.filter((file) => file.endsWith(".sql"))
);

context.logger.debug(`Found sql files`, {
sqlFiles,
});

const sqlDestinationPath = join(manifest.outputPath, "prisma", "sql");

for (const file of sqlFiles) {
const destination = join(sqlDestinationPath, file);
const source = join(prismaDir, "sql", file);

context.logger.debug(`Copying the sql from ${source} to ${destination}`);

await cp(source, destination);
}
}

if (usingSchemaFolder) {
const schemaDir = dirname(this._resolvedSchemaPath);

prismaDir = dirname(schemaDir);

context.logger.debug(`Using the schema folder: ${schemaDir}`);

// Find all the files in schemaDir that end with .prisma (excluding the schema.prisma file)
const prismaFiles = await readdir(schemaDir).then((files) =>
files.filter((file) => file.endsWith(".prisma"))
);

context.logger.debug(`Found prisma files in the schema folder`, {
prismaFiles,
});

const schemaDestinationPath = join(manifest.outputPath, "prisma", "schema");

const allPrismaFiles = [...prismaFiles];

for (const file of allPrismaFiles) {
const destination = join(schemaDestinationPath, file);
const source = join(schemaDir, file);

context.logger.debug(`Copying the prisma schema from ${source} to ${destination}`);

await cp(source, destination);
}

commands.push(
`${binaryForRuntime(
manifest.runtime
)} node_modules/prisma/build/index.js generate ${generatorFlags.join(" ")}` // Don't add the --schema flag or this will fail
);
} else {
prismaDir = dirname(this._resolvedSchemaPath);
// Now we need to add a layer that:
// Copies the prisma schema to the build outputPath
// Adds the `prisma` CLI dependency to the dependencies
// Adds the `prisma generate` command, which generates the Prisma client
const schemaDestinationPath = join(manifest.outputPath, "prisma", "schema.prisma");
// Copy the prisma schema to the build output path
context.logger.debug(
`Copying the prisma schema from ${this._resolvedSchemaPath} to ${schemaDestinationPath}`
);

await cp(this._resolvedSchemaPath, schemaDestinationPath);

commands.push(
`${binaryForRuntime(
manifest.runtime
)} node_modules/prisma/build/index.js generate --schema=./prisma/schema.prisma ${generatorFlags.join(
" "
)}`
);
}

const env: Record<string, string | undefined> = {};

// Copy schema.prisma to output
const schemaDestinationPath = join(manifest.outputPath, "prisma", "schema.prisma");
context.logger.debug(
`Copying the prisma schema from ${this._resolvedSchemaPath} to ${schemaDestinationPath}`
);
await cp(this._resolvedSchemaPath, schemaDestinationPath);
// Copy migrations if enabled
if (this.options.migrate) {
// Copy the migrations directory to the build output path
const migrationsDir = join(prismaDir, "migrations");
const migrationsDir = join(dirname(this._resolvedSchemaPath), "migrations");
const migrationsDestinationPath = join(manifest.outputPath, "prisma", "migrations");

context.logger.debug(
`Copying the prisma migrations from ${migrationsDir} to ${migrationsDestinationPath}`
);

await cp(migrationsDir, migrationsDestinationPath, { recursive: true });

commands.push(
`${binaryForRuntime(manifest.runtime)} node_modules/prisma/build/index.js migrate deploy`
);
}

// Generate Prisma client using explicit output and schema
commands.push(
`${binaryForRuntime(manifest.runtime)} node_modules/prisma/build/index.js generate --schema=./prisma/schema.prisma --output=${this._resolvedOutputPath} ${generatorFlags.join(" ")}`
);
// Set up environment variables
const env: Record<string, string | undefined> = {};
env.DATABASE_URL = manifest.deploy.env?.DATABASE_URL;

if (this.options.directUrlEnvVarName) {
env[this.options.directUrlEnvVarName] =
manifest.deploy.env?.[this.options.directUrlEnvVarName] ??
process.env[this.options.directUrlEnvVarName];

if (!env[this.options.directUrlEnvVarName]) {
context.logger.warn(
`prismaExtension could not resolve the ${this.options.directUrlEnvVarName} environment variable. Make sure you add it to your environment variables or provide it as an environment variable to the deploy CLI command. See our docs for more info: https://trigger.dev/docs/deploy-environment-variables`
Expand All @@ -245,21 +152,18 @@ export class PrismaExtension implements BuildExtension {
env.DIRECT_URL = manifest.deploy.env?.DIRECT_URL;
env.DIRECT_DATABASE_URL = manifest.deploy.env?.DIRECT_DATABASE_URL;
}

if (!env.DATABASE_URL) {
context.logger.warn(
`prismaExtension could not resolve the DATABASE_URL environment variable. Make sure you add it to your environment variables. See our docs for more info: https://trigger.dev/docs/deploy-environment-variables`
);
}

context.logger.debug(`Adding the prisma layer with the following commands`, {
commands,
env,
dependencies: {
prisma: version,
},
});

context.addLayer({
id: "prisma",
commands,
Expand Down