From 7b0071fd2734a4c7917a50a08e011ff77cd2c145 Mon Sep 17 00:00:00 2001 From: omar-dulaimi Date: Fri, 7 Nov 2025 14:43:44 +0300 Subject: [PATCH 1/2] feat: support prisma-client generator provider --- README.md | 9 ++++- prisma/schema.prisma | 3 +- src/index.ts | 1 - src/prisma-generator.ts | 15 +++++++- tests/basic-generation.test.ts | 14 +++---- tests/comprehensive-schema.test.ts | 14 +++---- tests/prisma-client-js-provider.test.ts | 34 +++++++++++++++++ tests/prisma-client-provider.test.ts | 38 +++++++++++++++++++ tests/relation-splitting.test.ts | 15 +++----- tests/schemas/basic.prisma | 5 ++- .../schemas/prisma-client-js-provider.prisma | 18 +++++++++ tests/schemas/prisma-client-provider.prisma | 19 ++++++++++ tests/self-relation.test.ts | 15 +++----- tests/setup.ts | 11 ++++++ tests/swagger-generation.test.ts | 22 ++++------- tests/utils/prisma-test-helpers.ts | 25 ++++++++++++ vitest.config.js | 3 +- 17 files changed, 205 insertions(+), 56 deletions(-) create mode 100644 tests/prisma-client-js-provider.test.ts create mode 100644 tests/prisma-client-provider.test.ts create mode 100644 tests/schemas/prisma-client-js-provider.prisma create mode 100644 tests/schemas/prisma-client-provider.prisma create mode 100644 tests/setup.ts create mode 100644 tests/utils/prisma-test-helpers.ts diff --git a/README.md b/README.md index ef46b1c..2a06f99 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,14 @@ pnpm add prisma-class-validator-generator 1. Add the generator to your Prisma schema: +> **Heads up:** Keep a Prisma Client generator in the same schema. Both `provider = "prisma-client"` (recommended) and `provider = "prisma-client-js"` are supported. + ```prisma +generator client { + provider = "prisma-client" // or "prisma-client-js" + output = "../generated/prisma-client" // required when using prisma-client +} + generator class_validator { provider = "prisma-class-validator-generator" output = "./generated" // optional, defaults to ./generated @@ -472,4 +479,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file 🐛 Report Issues💬 Discussions

- \ No newline at end of file + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6620013..0c512e1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,5 +1,6 @@ generator client { - provider = "prisma-client-js" + provider = "prisma-client" + output = "../generated/prisma-client" } generator class_validator { diff --git a/src/index.ts b/src/index.ts index 1d6d559..2c6745e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,6 @@ generatorHandler({ onManifest: () => ({ defaultOutput: './generated', prettyName: 'Prisma Class Validator Generator', - requiresGenerators: ['prisma-client-js'], }), onGenerate: generate, }); diff --git a/src/prisma-generator.ts b/src/prisma-generator.ts index 7286784..18aeb0a 100644 --- a/src/prisma-generator.ts +++ b/src/prisma-generator.ts @@ -15,6 +15,11 @@ export interface GeneratorConfig { separateRelationFields: boolean; } +const SUPPORTED_PRISMA_CLIENT_PROVIDERS = new Set([ + 'prisma-client', + 'prisma-client-js', +]); + export async function generate(options: GeneratorOptions) { const outputDir = parseEnvValue(options.generator.output as EnvValue); @@ -27,10 +32,16 @@ export async function generate(options: GeneratorOptions) { await fs.mkdir(outputDir, { recursive: true }); await removeDir(outputDir, true); - const prismaClientProvider = options.otherGenerators.find( - (it) => parseEnvValue(it.provider) === 'prisma-client-js', + const prismaClientProvider = options.otherGenerators.find((it) => + SUPPORTED_PRISMA_CLIENT_PROVIDERS.has(parseEnvValue(it.provider)), ); + if (!prismaClientProvider) { + throw new Error( + 'Prisma Class Validator Generator requires a Prisma Client generator with provider "prisma-client" or "prisma-client-js".', + ); + } + const prismaClientDmmf = await getDMMF({ datamodel: options.datamodel, previewFeatures: prismaClientProvider?.previewFeatures || [], diff --git a/tests/basic-generation.test.ts b/tests/basic-generation.test.ts index 0ed5495..58a7ecc 100644 --- a/tests/basic-generation.test.ts +++ b/tests/basic-generation.test.ts @@ -1,23 +1,21 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; import { existsSync, readFileSync } from 'fs'; import { describe, it, expect, beforeAll } from 'vitest'; import path from 'path'; - -const execAsync = promisify(exec); +import { + ensureGeneratorBuilt, + runPrismaGenerate, +} from './utils/prisma-test-helpers'; describe('Basic Class Validator Generation', () => { beforeAll(async () => { - // Build the generator first - await execAsync('npm run build'); + await ensureGeneratorBuilt(); }, 60000); it('should generate basic models with class validators', async () => { const schemaPath = path.resolve(__dirname, 'schemas/basic.prisma'); const outputPath = path.resolve(__dirname, 'generated/basic'); - // Generate using the basic schema - await execAsync(`npx prisma generate --schema="${schemaPath}"`); + await runPrismaGenerate({ schemaPath }); // Check if generated files exist expect(existsSync(path.join(outputPath, 'models', 'User.model.ts'))).toBe( diff --git a/tests/comprehensive-schema.test.ts b/tests/comprehensive-schema.test.ts index 6e45a20..c1a507e 100644 --- a/tests/comprehensive-schema.test.ts +++ b/tests/comprehensive-schema.test.ts @@ -1,23 +1,21 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; import { existsSync, readFileSync } from 'fs'; import { describe, it, expect, beforeAll } from 'vitest'; import path from 'path'; - -const execAsync = promisify(exec); +import { + ensureGeneratorBuilt, + runPrismaGenerate, +} from './utils/prisma-test-helpers'; describe('Comprehensive Schema Generation', () => { beforeAll(async () => { - // Build the generator first - await execAsync('npm run build'); + await ensureGeneratorBuilt(); }, 60000); it('should generate comprehensive models with enums and relations', async () => { const schemaPath = path.resolve(__dirname, 'schemas/comprehensive.prisma'); const outputPath = path.resolve(__dirname, 'generated/comprehensive'); - // Generate using the comprehensive schema - await execAsync(`npx prisma generate --schema="${schemaPath}"`); + await runPrismaGenerate({ schemaPath }); // Check if generated files exist expect(existsSync(path.join(outputPath, 'models', 'User.model.ts'))).toBe( diff --git a/tests/prisma-client-js-provider.test.ts b/tests/prisma-client-js-provider.test.ts new file mode 100644 index 0000000..5e37f19 --- /dev/null +++ b/tests/prisma-client-js-provider.test.ts @@ -0,0 +1,34 @@ +import { existsSync } from 'fs'; +import { describe, it, expect, beforeAll } from 'vitest'; +import path from 'path'; +import { + ensureGeneratorBuilt, + runPrismaGenerate, +} from './utils/prisma-test-helpers'; + +const schemaPath = path.resolve( + __dirname, + 'schemas/prisma-client-js-provider.prisma', +); +const classValidatorOutput = path.resolve( + __dirname, + 'generated/providers/prisma-client-js-class-validator', +); + + +describe('Prisma Client JS generator provider', () => { + beforeAll(async () => { + await ensureGeneratorBuilt(); + }, 60000); + + it('generates artifacts when using provider = "prisma-client-js"', async () => { + await runPrismaGenerate({ schemaPath }); + + expect( + existsSync(path.join(classValidatorOutput, 'models', 'User.model.ts')), + ).toBe(true); + expect( + existsSync(path.join(classValidatorOutput, 'helpers', 'index.ts')), + ).toBe(true); + }); +}); diff --git a/tests/prisma-client-provider.test.ts b/tests/prisma-client-provider.test.ts new file mode 100644 index 0000000..393b28a --- /dev/null +++ b/tests/prisma-client-provider.test.ts @@ -0,0 +1,38 @@ +import { existsSync } from 'fs'; +import { describe, it, expect, beforeAll } from 'vitest'; +import path from 'path'; +import { + ensureGeneratorBuilt, + runPrismaGenerate, +} from './utils/prisma-test-helpers'; + +const schemaPath = path.resolve( + __dirname, + 'schemas/prisma-client-provider.prisma', +); +const classValidatorOutput = path.resolve( + __dirname, + 'generated/providers/prisma-client-class-validator', +); +const prismaClientOutput = path.resolve( + __dirname, + 'generated/providers/prisma-client', +); + +describe('Prisma Client generator provider', () => { + beforeAll(async () => { + await ensureGeneratorBuilt(); + }, 60000); + + it('generates artifacts when using provider = "prisma-client"', async () => { + await runPrismaGenerate({ schemaPath }); + + expect( + existsSync(path.join(classValidatorOutput, 'models', 'User.model.ts')), + ).toBe(true); + expect( + existsSync(path.join(classValidatorOutput, 'helpers', 'index.ts')), + ).toBe(true); + expect(existsSync(prismaClientOutput)).toBe(true); + }); +}); diff --git a/tests/relation-splitting.test.ts b/tests/relation-splitting.test.ts index d0dcd1b..2274241 100644 --- a/tests/relation-splitting.test.ts +++ b/tests/relation-splitting.test.ts @@ -1,19 +1,16 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; import { existsSync, readFileSync } from 'fs'; import { describe, it, expect, beforeAll } from 'vitest'; import path from 'path'; - -const execAsync = promisify(exec); +import { + ensureGeneratorBuilt, + runPrismaGenerate, +} from './utils/prisma-test-helpers'; describe('Relation Splitting Generation', () => { beforeAll(async () => { - // Build the generator first - await execAsync('npm run build'); - - // Generate models for full-features schema + await ensureGeneratorBuilt(); const schemaPath = path.resolve(__dirname, 'schemas/full-features.prisma'); - await execAsync(`npx prisma generate --schema="${schemaPath}"`); + await runPrismaGenerate({ schemaPath }); }, 60000); it('should generate separate base and relation classes', () => { diff --git a/tests/schemas/basic.prisma b/tests/schemas/basic.prisma index 259a5fb..65f5e03 100644 --- a/tests/schemas/basic.prisma +++ b/tests/schemas/basic.prisma @@ -1,5 +1,6 @@ generator client { - provider = "prisma-client-js" + provider = "prisma-client" + output = "../generated/basic-client" } generator class_validator { @@ -30,4 +31,4 @@ model Post { author User? @relation(fields: [authorId], references: [id]) authorId Int? rating Float -} \ No newline at end of file +} diff --git a/tests/schemas/prisma-client-js-provider.prisma b/tests/schemas/prisma-client-js-provider.prisma new file mode 100644 index 0000000..f5755fc --- /dev/null +++ b/tests/schemas/prisma-client-js-provider.prisma @@ -0,0 +1,18 @@ +generator client { + provider = "prisma-client-js" +} + +generator class_validator { + provider = "node ./lib/generator.js" + output = "../generated/providers/prisma-client-js-class-validator" +} + +datasource db { + provider = "sqlite" + url = "file:./provider-client-js.db" +} + +model User { + id Int @id @default(autoincrement()) + email String @unique +} diff --git a/tests/schemas/prisma-client-provider.prisma b/tests/schemas/prisma-client-provider.prisma new file mode 100644 index 0000000..f7ad532 --- /dev/null +++ b/tests/schemas/prisma-client-provider.prisma @@ -0,0 +1,19 @@ +generator client { + provider = "prisma-client" + output = "../generated/providers/prisma-client" +} + +generator class_validator { + provider = "node ./lib/generator.js" + output = "../generated/providers/prisma-client-class-validator" +} + +datasource db { + provider = "sqlite" + url = "file:./provider-client.db" +} + +model User { + id Int @id @default(autoincrement()) + email String @unique +} diff --git a/tests/self-relation.test.ts b/tests/self-relation.test.ts index 67631a5..d52d964 100644 --- a/tests/self-relation.test.ts +++ b/tests/self-relation.test.ts @@ -1,19 +1,16 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; import { existsSync, readFileSync } from 'fs'; import { describe, it, expect, beforeAll } from 'vitest'; import path from 'path'; - -const execAsync = promisify(exec); +import { + ensureGeneratorBuilt, + runPrismaGenerate, +} from './utils/prisma-test-helpers'; describe('Self-Relation Generation', () => { beforeAll(async () => { - // Build the generator first - await execAsync('npm run build'); - - // Generate models for self-relation schema + await ensureGeneratorBuilt(); const schemaPath = path.resolve(__dirname, 'schemas/self-relation.prisma'); - await execAsync(`npx prisma generate --schema="${schemaPath}"`); + await runPrismaGenerate({ schemaPath }); }, 60000); it('should generate UserBase class without self-relations', () => { diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..bd4edf0 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,11 @@ +import { beforeEach, afterEach } from 'vitest'; + +const baseEnv = { ...process.env }; + +beforeEach(() => { + process.env = { ...baseEnv } as NodeJS.ProcessEnv; +}); + +afterEach(() => { + process.env = { ...baseEnv } as NodeJS.ProcessEnv; +}); diff --git a/tests/swagger-generation.test.ts b/tests/swagger-generation.test.ts index 97c91d5..0a3ad0d 100644 --- a/tests/swagger-generation.test.ts +++ b/tests/swagger-generation.test.ts @@ -1,19 +1,16 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; import { existsSync, readFileSync } from 'fs'; import { describe, it, expect, beforeAll } from 'vitest'; import path from 'path'; - -const execAsync = promisify(exec); +import { + ensureGeneratorBuilt, + runPrismaGenerate, +} from './utils/prisma-test-helpers'; describe('Swagger Generation', () => { beforeAll(async () => { - // Build the generator first - await execAsync('npm run build'); - - // Generate models for swagger schema + await ensureGeneratorBuilt(); const schemaPath = path.resolve(__dirname, 'schemas/swagger.prisma'); - await execAsync(`npx prisma generate --schema="${schemaPath}"`); + await runPrismaGenerate({ schemaPath }); }, 60000); it('should generate models with Swagger decorators when enabled', () => { @@ -73,12 +70,9 @@ describe('Swagger Generation', () => { describe('Combined Class with separateRelationFields', () => { beforeAll(async () => { - // Build the generator first - await execAsync('npm run build'); - - // Generate models for full-features schema (has both swagger and separateRelationFields) + await ensureGeneratorBuilt(); const schemaPath = path.resolve(__dirname, 'schemas/full-features.prisma'); - await execAsync(`npx prisma generate --schema="${schemaPath}"`); + await runPrismaGenerate({ schemaPath }); }, 60000); it('should generate combined class with class-validator and Swagger decorators for relations', () => { diff --git a/tests/utils/prisma-test-helpers.ts b/tests/utils/prisma-test-helpers.ts new file mode 100644 index 0000000..cfcbf1b --- /dev/null +++ b/tests/utils/prisma-test-helpers.ts @@ -0,0 +1,25 @@ +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +let buildPromise: Promise | null = null; + +export async function ensureGeneratorBuilt() { + if (!buildPromise) { + buildPromise = execAsync('npm run build'); + } + return buildPromise; +} + +interface GenerateOptions { + schemaPath: string; + env?: NodeJS.ProcessEnv; +} + +export async function runPrismaGenerate(options: GenerateOptions) { + const { schemaPath, env } = options; + await execAsync(`npx prisma generate --schema="${schemaPath}"`, { + env: { ...process.env, ...env }, + }); +} diff --git a/vitest.config.js b/vitest.config.js index a08032d..18e3da9 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -6,6 +6,7 @@ export default defineConfig({ testTimeout: 300000, // 5 minutes for complex generation tests hookTimeout: 60000, // 1 minute for setup/teardown globals: true, + setupFiles: ['tests/setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], @@ -19,4 +20,4 @@ export default defineConfig({ ] } } -}); \ No newline at end of file +}); From fc8422097fcea1771c90b2a3139ff7153d4939ec Mon Sep 17 00:00:00 2001 From: omar-dulaimi Date: Fri, 7 Nov 2025 14:47:04 +0300 Subject: [PATCH 2/2] chore: format provider test --- tests/prisma-client-js-provider.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/prisma-client-js-provider.test.ts b/tests/prisma-client-js-provider.test.ts index 5e37f19..45cdae0 100644 --- a/tests/prisma-client-js-provider.test.ts +++ b/tests/prisma-client-js-provider.test.ts @@ -15,7 +15,6 @@ const classValidatorOutput = path.resolve( 'generated/providers/prisma-client-js-class-validator', ); - describe('Prisma Client JS generator provider', () => { beforeAll(async () => { await ensureGeneratorBuilt();