From 80cd7fe00515a95c91ba7e8382c71942c531371a Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 20:48:19 +0700 Subject: [PATCH 1/8] add single-file output config --- packages/generator/src/lib/config.ts | 5 ++- .../src/shared/generator-context/index.ts | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/generator/src/lib/config.ts b/packages/generator/src/lib/config.ts index b46da21..a8ee5f1 100644 --- a/packages/generator/src/lib/config.ts +++ b/packages/generator/src/lib/config.ts @@ -3,14 +3,13 @@ import { type Output, type SchemaIssues, flatten, + literal, object, optional, safeParse, - literal, } from 'valibot' import { ModuleResolution } from '~/shared/generator-context/module-resolution' -import { withDefault } from './valibot-schema' -import { BooleanInStr } from './valibot-schema' +import { BooleanInStr, withDefault } from './valibot-schema' const Config = object({ relationalQuery: withDefault(optional(BooleanInStr), true), diff --git a/packages/generator/src/shared/generator-context/index.ts b/packages/generator/src/shared/generator-context/index.ts index 670f9c3..c89d351 100644 --- a/packages/generator/src/shared/generator-context/index.ts +++ b/packages/generator/src/shared/generator-context/index.ts @@ -2,9 +2,18 @@ import type { GeneratorOptions } from '@prisma/generator-helper' import { type Config, parseConfig } from '~/lib/config' import { resolveModuleResolution } from './module-resolution' +type Output = { + isSingleFile: boolean + path: string +} + type Generator = { + /** + * @deprecated use `output.basePath` instead + */ outputBasePath: string moduleResolution?: string + output: Output // dmmf: GeneratorOptions['dmmf'] config: Config @@ -16,10 +25,12 @@ let generator_: Generator | undefined export function initializeGenerator(options: GeneratorOptions) { const config = parseConfig(options.generator.config) + const output = getOutputConfig(options) const context: Generator = { moduleResolution: config.moduleResolution ?? resolveModuleResolution(), - outputBasePath: getBasePath(options), + outputBasePath: output.path, + output, // dmmf: options.dmmf, config, @@ -29,10 +40,27 @@ export function initializeGenerator(options: GeneratorOptions) { return context } -function getBasePath(options: GeneratorOptions) { +function getOutputConfig(options: GeneratorOptions): Output { const basePath = options.generator.output?.value if (!basePath) throw new Error('No output path specified') - return basePath + + const isSingleFile = basePath.endsWith('.ts') + if (isSingleFile) { + return { + isSingleFile: true, + path: basePath, + } + } + + return { + isSingleFile: false, + path: basePath, + } +} + +function getParent(basePath: string) { + const paths = basePath.split('/') + return paths.length === 1 ? ['./'] : paths.slice(0, -1) } // #endregion From e5f9acd452c5b6dd85de1d52659765c1508b1b33 Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 21:03:54 +0700 Subject: [PATCH 2/8] handle single file output --- packages/generator/src/generator.ts | 84 ++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/packages/generator/src/generator.ts b/packages/generator/src/generator.ts index 6faac28..11628a1 100644 --- a/packages/generator/src/generator.ts +++ b/packages/generator/src/generator.ts @@ -14,26 +14,26 @@ import { type ModelModule, generateModelModules, } from './lib/adapter/modules/model' +import { + type RelationalModule, + generateRelationalModules, +} from './lib/adapter/modules/relational' +import { generateImplicitModules } from './lib/adapter/modules/relational' +import type { RelationalModuleSet } from './lib/adapter/modules/relational' +import { generateSchemaModules as generateSchemaModule } from './lib/adapter/modules/relational' +import type { BaseGeneratedModules } from './lib/adapter/modules/sets/base-generated-modules' import { logger } from './lib/logger' import { type ImportValue, type NamedImport, namedImport, } from './lib/syntaxes/imports' -import type { Module } from './lib/syntaxes/module' +import { type Module, createModule } from './lib/syntaxes/module' import { - isRelationalQueryEnabled, - initializeGenerator, getGenerator, + initializeGenerator, + isRelationalQueryEnabled, } from './shared/generator-context' -import { - generateRelationalModules, - type RelationalModule, -} from './lib/adapter/modules/relational' -import type { BaseGeneratedModules } from './lib/adapter/modules/sets/base-generated-modules' -import { generateImplicitModules } from './lib/adapter/modules/relational' -import type { RelationalModuleSet } from './lib/adapter/modules/relational' -import { generateSchemaModules as generateSchemaModule } from './lib/adapter/modules/relational' const { version } = require('../package.json') @@ -60,18 +60,21 @@ generatorHandler({ if (isRelationalQueryEnabled()) { const relational = generateRelationalModules(modules.models) - const implicit = generateImplicitModules(adapter, relational) - const schema = generateSchemaModule({ - ...modules, - relational: relational, - implicitModels: implicit.models, - implicitRelational: implicit.relational, - }) - - modules.schema = schema modules.relational = relational + + const implicit = generateImplicitModules(adapter, relational) modules.implicitModels = implicit.models modules.implicitRelational = implicit.relational + + if (!getGenerator().output.isSingleFile) { + const schema = generateSchemaModule({ + ...modules, + relational: relational, + implicitModels: implicit.models, + implicitRelational: implicit.relational, + }) + modules.schema = schema + } } writeModules(modules) @@ -122,17 +125,50 @@ export function reduceImports(imports: ImportValue[]) { } function writeModules(modules: GeneratedModules) { - const basePath = getGenerator().outputBasePath + const outputPath = getGenerator().output.path - fs.existsSync(basePath) && fs.rmSync(basePath, { recursive: true }) - fs.mkdirSync(basePath, { recursive: true }) + if (getGenerator().output.isSingleFile) { + fs.writeFileSync(outputPath, createDrizzleModule(modules).code) + return + } + + fs.existsSync(outputPath) && fs.rmSync(outputPath, { recursive: true }) + fs.mkdirSync(outputPath, { recursive: true }) for (const module of flattenModules(modules)) { - const writeLocation = path.join(basePath, `${module.name}.ts`) + const writeLocation = path.join(outputPath, `${module.name}.ts`) fs.writeFileSync(writeLocation, module.code) } } +/** + * A single file output module where it contains + * all schema definitions and its relations + */ +function createDrizzleModule(modules: GeneratedModules) { + return createModule({ + name: getSingleOutputFileName(), + declarations: flattenModules(modules).flatMap((module) => + module.declarations.map((declaration) => { + return { + ...declaration, + imports: declaration.imports.filter( + (i) => !i.module.startsWith('./') + ), + } + }) + ), + }) +} + +function getSingleOutputFileName() { + const lastSegment = getGenerator().output.path.split('/').at(-1) + if (lastSegment == null) { + throw new Error('Last segment is undefined') + } + return lastSegment.replace('.ts', '') +} + /** * @dev Importing the adapter dynamically so `getGeneratorContext()` won't * be called before initialization (`onGenerate`) From fc26d97be9aee284dde51b3c1d3e800536a4bb10 Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 21:04:35 +0700 Subject: [PATCH 3/8] remove unused --- packages/generator/src/shared/generator-context/index.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/generator/src/shared/generator-context/index.ts b/packages/generator/src/shared/generator-context/index.ts index c89d351..207b06b 100644 --- a/packages/generator/src/shared/generator-context/index.ts +++ b/packages/generator/src/shared/generator-context/index.ts @@ -58,11 +58,6 @@ function getOutputConfig(options: GeneratorOptions): Output { } } -function getParent(basePath: string) { - const paths = basePath.split('/') - return paths.length === 1 ? ['./'] : paths.slice(0, -1) -} - // #endregion export function getGenerator() { From c793757b8c0b7ea27d77984c01e4c3255bd07b7a Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 21:37:46 +0700 Subject: [PATCH 4/8] add test --- packages/usage/.gitignore | 3 ++ .../usage/tests/single-file-output.test.ts | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 packages/usage/tests/single-file-output.test.ts diff --git a/packages/usage/.gitignore b/packages/usage/.gitignore index 084f8fc..ed357cf 100644 --- a/packages/usage/.gitignore +++ b/packages/usage/.gitignore @@ -7,3 +7,6 @@ prisma/mysql/schema.prisma prisma/sqlite/schema.prisma prisma/sqlite/test.db prisma/sqlite/test.db-journal + +# Generated on-the-fly +.temp \ No newline at end of file diff --git a/packages/usage/tests/single-file-output.test.ts b/packages/usage/tests/single-file-output.test.ts new file mode 100644 index 0000000..b2f9219 --- /dev/null +++ b/packages/usage/tests/single-file-output.test.ts @@ -0,0 +1,29 @@ +import fs from 'node:fs' +import { createId } from '@paralleldrive/cuid2' +import { $ } from 'bun' + +afterAll(() => { + fs.rmSync('.temp', { recursive: true, force: true }) +}) + +test('generates drizzle.ts', async () => { + const name = createId() + writeSchemaWithOutput(name, 'drizzle.ts') + await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + + expect(fs.existsSync(`.temp/${name}/drizzle.ts`)).toBe(true) +}) + +function writeSchemaWithOutput(name: string, output: string) { + if (!fs.existsSync(`.temp/${name}`)) { + fs.mkdirSync(`.temp/${name}`, { recursive: true }) + } + + const schema = fs + .readFileSync('./prisma/schema.prisma', { encoding: 'utf-8' }) + .replace( + 'generator drizzle {\n provider = "bunx prisma-generator-drizzle"\n}', + `generator drizzle {\n provider = "bunx prisma-generator-drizzle"\n output = "${output}"\n}` + ) + fs.writeFileSync(`.temp/${name}/schema.prisma`, schema) +} From fbf386b475e24272b5231255a4ea15719a6d3464 Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 21:50:18 +0700 Subject: [PATCH 5/8] add more test --- .../usage/tests/single-file-output.test.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/usage/tests/single-file-output.test.ts b/packages/usage/tests/single-file-output.test.ts index b2f9219..28f0435 100644 --- a/packages/usage/tests/single-file-output.test.ts +++ b/packages/usage/tests/single-file-output.test.ts @@ -14,8 +14,36 @@ test('generates drizzle.ts', async () => { expect(fs.existsSync(`.temp/${name}/drizzle.ts`)).toBe(true) }) +test('generates ./drizzle.ts', async () => { + const name = createId() + writeSchemaWithOutput(name, './drizzle.ts') + await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + + expect(fs.existsSync(`.temp/${name}/drizzle.ts`)).toBe(true) +}) + +test('generates sub/drizzle.ts', async () => { + const name = createId() + writeSchemaWithOutput(name, 'sub/drizzle.ts') + await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + + expect(fs.existsSync(`.temp/${name}/sub/drizzle.ts`)).toBe(true) +}) + +test('generates ./sub/drizzle.ts', async () => { + const name = createId() + writeSchemaWithOutput(name, './sub/drizzle.ts') + await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + + expect(fs.existsSync(`.temp/${name}/sub/drizzle.ts`)).toBe(true) +}) + function writeSchemaWithOutput(name: string, output: string) { - if (!fs.existsSync(`.temp/${name}`)) { + if (hasSubFolder(output)) { + fs.mkdirSync(`.temp/${name}/${getParentPath(output)}`, { + recursive: true, + }) + } else { fs.mkdirSync(`.temp/${name}`, { recursive: true }) } @@ -27,3 +55,10 @@ function writeSchemaWithOutput(name: string, output: string) { ) fs.writeFileSync(`.temp/${name}/schema.prisma`, schema) } +function getParentPath(output: string) { + return output.split('/')[0] +} + +function hasSubFolder(output: string) { + return output.split('/').length > 1 +} From 245199c13382568f9af957372e5448af8e9b70ca Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 21:56:48 +0700 Subject: [PATCH 6/8] fixes --- packages/generator/src/generator.ts | 12 ++++++++++ .../usage/tests/single-file-output.test.ts | 23 ++++++++----------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/generator/src/generator.ts b/packages/generator/src/generator.ts index 11628a1..317c12e 100644 --- a/packages/generator/src/generator.ts +++ b/packages/generator/src/generator.ts @@ -128,6 +128,10 @@ function writeModules(modules: GeneratedModules) { const outputPath = getGenerator().output.path if (getGenerator().output.isSingleFile) { + if (hasSubFolder(outputPath)) { + fs.mkdirSync(getParentPath(outputPath), { recursive: true }) + } + fs.writeFileSync(outputPath, createDrizzleModule(modules).code) return } @@ -141,6 +145,14 @@ function writeModules(modules: GeneratedModules) { } } +function getParentPath(output: string) { + return output.split('/').slice(0, -1).join('/') +} + +function hasSubFolder(output: string) { + return output.split('/').length > 1 +} + /** * A single file output module where it contains * all schema definitions and its relations diff --git a/packages/usage/tests/single-file-output.test.ts b/packages/usage/tests/single-file-output.test.ts index 28f0435..e0b535b 100644 --- a/packages/usage/tests/single-file-output.test.ts +++ b/packages/usage/tests/single-file-output.test.ts @@ -38,14 +38,16 @@ test('generates ./sub/drizzle.ts', async () => { expect(fs.existsSync(`.temp/${name}/sub/drizzle.ts`)).toBe(true) }) +test('generates ./sub/multi/drizzle.ts', async () => { + const name = createId() + writeSchemaWithOutput(name, './sub/multi/drizzle.ts') + await $`bun prisma generate --schema .temp/${name}/schema.prisma`.quiet() + + expect(fs.existsSync(`.temp/${name}/sub/multi/drizzle.ts`)).toBe(true) +}) + function writeSchemaWithOutput(name: string, output: string) { - if (hasSubFolder(output)) { - fs.mkdirSync(`.temp/${name}/${getParentPath(output)}`, { - recursive: true, - }) - } else { - fs.mkdirSync(`.temp/${name}`, { recursive: true }) - } + fs.mkdirSync(`.temp/${name}`, { recursive: true }) const schema = fs .readFileSync('./prisma/schema.prisma', { encoding: 'utf-8' }) @@ -55,10 +57,3 @@ function writeSchemaWithOutput(name: string, output: string) { ) fs.writeFileSync(`.temp/${name}/schema.prisma`, schema) } -function getParentPath(output: string) { - return output.split('/')[0] -} - -function hasSubFolder(output: string) { - return output.split('/').length > 1 -} From a21b935841ae1d61477b31e33d9439add6fe2fe0 Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 22:06:08 +0700 Subject: [PATCH 7/8] fix test --- packages/usage/package.json | 6 +++--- packages/usage/prisma/schema.prisma | 2 +- packages/usage/scripts/cloneMysql.ts | 1 + packages/usage/scripts/cloneSqlite.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/usage/package.json b/packages/usage/package.json index 62bc72b..14887ed 100644 --- a/packages/usage/package.json +++ b/packages/usage/package.json @@ -8,9 +8,9 @@ "start": "bun run src/index.ts", "test": "bun test --preload tests/setup.ts", "generate": "prisma generate", - "pushreset:postgres": "DATABASE_URL=$PG_DATABASE_URL bun prisma db push --schema prisma/schema.prisma --force-reset --accept-data-loss", - "pushreset:sqlite": "DATABASE_URL=$MYSQL_DATABASE_URL bun prisma db push --schema prisma/mysql/schema.prisma --force-reset --accept-data-loss", - "pushreset:mysql": "bun prisma db push --schema prisma/sqlite/schema.prisma --force-reset --accept-data-loss", + "pushreset:postgres": "bun prisma db push --schema prisma/schema.prisma --force-reset --accept-data-loss", + "pushreset:mysql": "bun prisma db push --schema prisma/mysql/schema.prisma --force-reset --accept-data-loss", + "pushreset:sqlite": "bun prisma db push --schema prisma/sqlite/schema.prisma --force-reset --accept-data-loss", "clone:sqlite": "bun run scripts/cloneSqlite.ts", "clone:mysql": "bun run scripts/cloneMysql.ts" }, diff --git a/packages/usage/prisma/schema.prisma b/packages/usage/prisma/schema.prisma index 074a1b7..9ab0ea1 100644 --- a/packages/usage/prisma/schema.prisma +++ b/packages/usage/prisma/schema.prisma @@ -4,7 +4,7 @@ generator drizzle { datasource db { provider = "postgresql" - url = env("DATABASE_URL") + url = env("PG_DATABASE_URL") } model SelfReference { diff --git a/packages/usage/scripts/cloneMysql.ts b/packages/usage/scripts/cloneMysql.ts index d64cfbd..ca59b31 100644 --- a/packages/usage/scripts/cloneMysql.ts +++ b/packages/usage/scripts/cloneMysql.ts @@ -5,6 +5,7 @@ const TARGET_PATH = './prisma/mysql/schema.prisma' const schema = (await Bun.file(BASE_PATH).text()) .replace('postgresql', 'mysql') + .replace('PG_DATABASE_URL', 'MYSQL_DATABASE_URL') .replace(/(\/\/ start -mysql\n)[\s\S]*?(\n\/\/ end -mysql)/g, '') .replace(/^.*-mysql.*/gm, '') diff --git a/packages/usage/scripts/cloneSqlite.ts b/packages/usage/scripts/cloneSqlite.ts index dad778c..9998cf5 100644 --- a/packages/usage/scripts/cloneSqlite.ts +++ b/packages/usage/scripts/cloneSqlite.ts @@ -5,7 +5,7 @@ const TARGET_PATH = './prisma/sqlite/schema.prisma' const schema = (await Bun.file(BASE_PATH).text()) .replace('postgresql', 'sqlite') - .replace('env("DATABASE_URL")', '"file:./test.db"') + .replace('env("PG_DATABASE_URL")', '"file:./test.db"') .replace(/(\/\/ start -sqlite\n)[\s\S]*?(\n\/\/ end -sqlite)/g, '') .replace(/^.*-sqlite.*/gm, '') From fac315249e870f2ec0a26f2aaaf6ae3bed1d658c Mon Sep 17 00:00:00 2001 From: farreldarian Date: Tue, 9 Apr 2024 22:07:46 +0700 Subject: [PATCH 8/8] remove unused --- packages/generator/src/shared/generator-context/index.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/generator/src/shared/generator-context/index.ts b/packages/generator/src/shared/generator-context/index.ts index 207b06b..9f0a521 100644 --- a/packages/generator/src/shared/generator-context/index.ts +++ b/packages/generator/src/shared/generator-context/index.ts @@ -8,10 +8,6 @@ type Output = { } type Generator = { - /** - * @deprecated use `output.basePath` instead - */ - outputBasePath: string moduleResolution?: string output: Output // @@ -29,7 +25,6 @@ export function initializeGenerator(options: GeneratorOptions) { const context: Generator = { moduleResolution: config.moduleResolution ?? resolveModuleResolution(), - outputBasePath: output.path, output, // dmmf: options.dmmf,