Skip to content

Commit

Permalink
feat(cli): add prismaSchemaFolder support to prisma generate via …
Browse files Browse the repository at this point in the history
…`mergeSchemas` (#24127)

* feat(cli): add prismaSchemaFolder support to "prisma generate" via "mergeSchemas"

* chore: fix build errors

* feat(cli): add "prismaSchemaFolder" support to "prisma generate"

* fix(client): getSchemaPath usage in "generateInFolder"

* chore(review): allow linting changes to Generatable interface; address old TODO by renaming it to Generable

* chore(review): adjust "getSchemaPath" usage for "getGenerators"

* chore: removed comment

* fix(client): update snapshot

* chore: fix lint
  • Loading branch information
jkomyno committed May 10, 2024
1 parent 23aba0b commit 6fcc818
Show file tree
Hide file tree
Showing 21 changed files with 219 additions and 93 deletions.
1 change: 0 additions & 1 deletion packages/cli/src/Generate.ts
Expand Up @@ -139,7 +139,6 @@ ${bold('Examples')}
let clientGeneratorVersion: string | null = null
try {
generators = await getGenerators({
// TODO: pass the schemas rather than the schemaPath
schemaPath,
printDownloadProgress: !watchMode,
version: enginesVersion,
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/utils/test-handlePanic.ts
Expand Up @@ -13,6 +13,7 @@ async function main() {

process.chdir(dirPath)

// TODO: update with `getSchema`.
const schemaPath = path.join(dirPath, 'schema.prisma')
const schema = fs.readFileSync(schemaPath, 'utf-8')

Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/__tests__/generation/generator.test.ts
Expand Up @@ -126,15 +126,15 @@ describe('generator', () => {
"Prisma schema validation - (get-dmmf wasm)
Error code: P1012
error: Error validating model "public": The model name \`public\` is invalid. It is a reserved name. Please change it. Read more at https://pris.ly/d/naming-models
--> schema.prisma:10
--> src/__tests__/generation/denylist.prisma:10
|
9 |
10 | model public {
11 | id Int @id
12 | }
|
error: Error validating model "return": The model name \`return\` is invalid. It is a reserved name. Please change it. Read more at https://pris.ly/d/naming-models
--> schema.prisma:14
--> src/__tests__/generation/denylist.prisma:14
|
13 |
14 | model return {
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/generation/TSClient/Count.ts
Expand Up @@ -5,11 +5,11 @@ import * as ts from '../ts-builders'
import { capitalize, getFieldArgName, getSelectName } from '../utils'
import { ArgsTypeBuilder } from './Args'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import type { Generable } from './Generable'
import { GenerateContext } from './GenerateContext'
import { buildOutputType } from './Output'

export class Count implements Generatable {
export class Count implements Generable {
constructor(protected readonly type: DMMF.OutputType, protected readonly context: GenerateContext) {}
protected get argsTypes(): ts.Export<ts.TypeDeclaration>[] {
const argsTypes: ts.Export<ts.TypeDeclaration>[] = []
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/generation/TSClient/Datasources.ts
@@ -1,9 +1,9 @@
import { DataSource } from '@prisma/generator-helper'
import indent from 'indent-string'

import type { Generatable } from './Generatable'
import type { Generable } from './Generable'

export class Datasources implements Generatable {
export class Datasources implements Generable {
constructor(protected readonly internalDatasources: DataSource[]) {}
public toTS(): string {
const sources = this.internalDatasources
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/generation/TSClient/Enum.ts
Expand Up @@ -4,9 +4,9 @@ import { objectEnumNames } from '../../runtime/core/types/exported/ObjectEnums'
import { strictEnumNames } from '../../runtime/strictEnum'
import type { DMMF } from '../dmmf-types'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import type { Generable } from './Generable'

export class Enum implements Generatable {
export class Enum implements Generable {
constructor(protected readonly type: DMMF.SchemaEnum, protected readonly useNamespace: boolean) {}

private isObjectEnum(): boolean {
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/generation/TSClient/FieldRefInput.ts
@@ -1,8 +1,8 @@
import type { DMMF } from '../dmmf-types'
import { getRefAllowedTypeName } from '../utils'
import type { Generatable } from './Generatable'
import type { Generable } from './Generable'

export class FieldRefInput implements Generatable {
export class FieldRefInput implements Generable {
constructor(private type: DMMF.FieldRefType) {}

toTS() {
Expand Down
18 changes: 18 additions & 0 deletions packages/client/src/generation/TSClient/Generable.ts
@@ -0,0 +1,18 @@
export interface Generable {
toJS?(): string
toTS(): string
toBrowserJS?(): string
toTSWithoutNamespace?(): string
}

export function JS(gen: Generable): string {
return gen.toJS?.() ?? ''
}

export function BrowserJS(gen: Generable): string {
return gen.toBrowserJS?.() ?? ''
}

export function TS(gen: Generable): string {
return gen.toTS()
}
19 changes: 0 additions & 19 deletions packages/client/src/generation/TSClient/Generatable.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/client/src/generation/TSClient/Input.ts
Expand Up @@ -6,9 +6,9 @@ import { GenericArgsInfo } from '../GenericsArgsInfo'
import * as ts from '../ts-builders'
import { GraphQLScalarToJSTypeTable, JSOutputTypeToInputType } from '../utils/common'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import type { Generable } from './Generable'

export class InputField implements Generatable {
export class InputField implements Generable {
constructor(
protected readonly field: DMMF.SchemaArg,
protected readonly genericsInfo: GenericArgsInfo,
Expand Down Expand Up @@ -119,7 +119,7 @@ function xorTypes(types: ts.TypeBuilder[]) {
return types.reduce((prev, curr) => ts.namedType('XOR').addGenericArgument(prev).addGenericArgument(curr))
}

export class InputType implements Generatable {
export class InputType implements Generable {
private generatedName: string
constructor(protected readonly type: DMMF.InputType, protected readonly genericsInfo: GenericArgsInfo) {
this.generatedName = type.name
Expand Down
6 changes: 3 additions & 3 deletions packages/client/src/generation/TSClient/Model.ts
Expand Up @@ -29,7 +29,7 @@ import {
import { InputField } from './../TSClient'
import { ArgsTypeBuilder } from './Args'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import type { Generable } from './Generable'
import { GenerateContext } from './GenerateContext'
import { getArgFieldJSDoc, getArgs, getGenericMethod, getMethodJSDoc, wrapComment } from './helpers'
import { InputType } from './Input'
Expand All @@ -39,7 +39,7 @@ import { buildModelPayload } from './Payload'
import { buildIncludeType, buildOmitType, buildScalarSelectType, buildSelectType } from './SelectIncludeOmit'
import { getModelActions } from './utils/getModelActions'

export class Model implements Generatable {
export class Model implements Generable {
protected type: DMMF.OutputType
protected mapping?: DMMF.ModelMapping
private dmmf: DMMFHelper
Expand Down Expand Up @@ -357,7 +357,7 @@ ${this.argsTypes.map((type) => ts.stringify(type)).join('\n\n')}
`
}
}
export class ModelDelegate implements Generatable {
export class ModelDelegate implements Generable {
constructor(protected readonly outputType: DMMF.OutputType, protected readonly context: GenerateContext) {}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/generation/TSClient/ModelFieldRefs.ts
@@ -1,8 +1,8 @@
import { DMMF } from '../dmmf-types'
import { getFieldRefsTypeName, getRefAllowedTypeName } from '../utils'
import { Generatable } from './Generatable'
import { Generable } from './Generable'

export class ModelFieldRefs implements Generatable {
export class ModelFieldRefs implements Generable {
constructor(protected outputType: DMMF.OutputType) {}
toTS() {
const { name } = this.outputType
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/generation/TSClient/PrismaClient.ts
Expand Up @@ -18,7 +18,7 @@ import { lowerCase } from '../utils/common'
import { runtimeImport } from '../utils/runtimeImport'
import { TAB_SIZE } from './constants'
import { Datasources } from './Datasources'
import type { Generatable } from './Generatable'
import type { Generable } from './Generable'
import { TSClientOptions } from './TSClient'
import { getModelActions } from './utils/getModelActions'

Expand Down Expand Up @@ -331,7 +331,7 @@ function eventRegistrationMethodDeclaration(runtimeNameTs: TSClientOptions['runt
}
}

export class PrismaClientClass implements Generatable {
export class PrismaClientClass implements Generable {
protected clientExtensionsDefinitions: {
prismaNamespaceDefinitions: string
prismaClientDefinitions: string
Expand Down
9 changes: 4 additions & 5 deletions packages/client/src/generation/TSClient/TSClient.ts
Expand Up @@ -2,7 +2,6 @@ import type { BinaryTarget } from '@prisma/get-platform'
import { ClientEngineType, getClientEngineType, getEnvPaths, pathToPosix } from '@prisma/internals'
import ciInfo from 'ci-info'
import crypto from 'crypto'
import { readFile } from 'fs/promises'
import indent from 'indent-string'
import path from 'path'
import { O } from 'ts-toolbelt'
Expand All @@ -27,7 +26,7 @@ import { Count } from './Count'
import { DefaultArgsAliases } from './DefaultArgsAliases'
import { Enum } from './Enum'
import { FieldRefInput } from './FieldRefInput'
import { type Generatable } from './Generatable'
import { type Generable } from './Generable'
import { GenerateContext } from './GenerateContext'
import { InputType } from './Input'
import { Model } from './Model'
Expand All @@ -52,7 +51,7 @@ export type TSClientOptions = O.Required<GenerateClientOptions, 'runtimeBase'> &
reusedJs?: string // the entrypoint to reuse
}

export class TSClient implements Generatable {
export class TSClient implements Generable {
protected readonly dmmf: DMMFHelper
protected readonly genericsInfo: GenericArgsInfo

Expand All @@ -61,14 +60,15 @@ export class TSClient implements Generatable {
this.genericsInfo = new GenericArgsInfo(this.dmmf)
}

public async toJS(): Promise<string> {
public toJS(): string {
const {
edge,
wasm,
binaryPaths,
generator,
outputDir,
schemaPath,
datamodel: inlineSchema,
runtimeBase,
runtimeNameJs,
datasources,
Expand Down Expand Up @@ -97,7 +97,6 @@ export class TSClient implements Generatable {
? (Object.keys(binaryPaths.libqueryEngine ?? {}) as BinaryTarget[])
: (Object.keys(binaryPaths.queryEngine ?? {}) as BinaryTarget[])

const inlineSchema = await readFile(schemaPath, 'utf8')
const inlineSchemaHash = crypto
.createHash('sha256')
.update(Buffer.from(inlineSchema, 'utf8').toString('base64'))
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/generation/TSClient/index.ts
@@ -1,7 +1,7 @@
import 'flat-map-polyfill' // unfortunately needed as it's not properly polyfilled in TypeScript

export { Enum } from './Enum'
export { BrowserJS, JS, TS } from './Generatable'
export { BrowserJS, JS, TS } from './Generable'
export { InputField, InputType } from './Input'
export { Model, ModelDelegate } from './Model'
export { TSClient } from './TSClient'
34 changes: 16 additions & 18 deletions packages/client/src/generation/generateClient.ts
Expand Up @@ -171,17 +171,17 @@ export async function buildClient({

// we store the generated contents here
const fileMap: Record<string, string> = {}
fileMap['index.js'] = await JS(nodeClient)
fileMap['index.d.ts'] = await TS(nodeClient)
fileMap['default.js'] = await JS(defaultClient)
fileMap['default.d.ts'] = await TS(defaultClient)
fileMap['index-browser.js'] = await BrowserJS(nodeClient)
fileMap['edge.js'] = await JS(edgeClient)
fileMap['edge.d.ts'] = await TS(edgeClient)
fileMap['index.js'] = JS(nodeClient)
fileMap['index.d.ts'] = TS(nodeClient)
fileMap['default.js'] = JS(defaultClient)
fileMap['default.d.ts'] = TS(defaultClient)
fileMap['index-browser.js'] = BrowserJS(nodeClient)
fileMap['edge.js'] = JS(edgeClient)
fileMap['edge.d.ts'] = TS(edgeClient)

if (generator.previewFeatures.includes('reactNative')) {
fileMap['react-native.js'] = await JS(rnTsClient)
fileMap['react-native.d.ts'] = await TS(rnTsClient)
fileMap['react-native.js'] = JS(rnTsClient)
fileMap['react-native.d.ts'] = TS(rnTsClient)
}

if (generator.previewFeatures.includes('driverAdapters')) {
Expand All @@ -198,8 +198,8 @@ export async function buildClient({
// - Always using #main-entry-point is kept for GA (small breaking change).
// - exportsMapDefault can be inlined down below and MUST be removed elsewhere.
// In short: A lot can be simplified, but can only happen in GA & P6.
fileMap['default.js'] = await JS(trampolineTsClient)
fileMap['default.d.ts'] = await TS(trampolineTsClient)
fileMap['default.js'] = JS(trampolineTsClient)
fileMap['default.d.ts'] = TS(trampolineTsClient)
fileMap['wasm-worker-loader.js'] = `export default (await import('./query_engine_bg.wasm')).default`
fileMap['wasm-edge-light-loader.js'] = `export default (await import('./query_engine_bg.wasm?module')).default`
pkgJson['browser'] = 'default.js' // also point to the trampoline client otherwise it is picked up by cfw
Expand All @@ -224,8 +224,8 @@ export async function buildClient({
wasm: true,
})

fileMap['wasm.js'] = await JS(wasmClient)
fileMap['wasm.d.ts'] = await TS(wasmClient)
fileMap['wasm.js'] = JS(wasmClient)
fileMap['wasm.d.ts'] = TS(wasmClient)
} else {
fileMap['wasm.js'] = fileMap['index-browser.js']
fileMap['wasm.d.ts'] = fileMap['default.d.ts']
Expand All @@ -242,8 +242,8 @@ export async function buildClient({
edge: true,
})

fileMap['deno/edge.js'] = await JS(denoEdgeClient)
fileMap['deno/index.d.ts'] = await TS(denoEdgeClient)
fileMap['deno/edge.js'] = JS(denoEdgeClient)
fileMap['deno/index.d.ts'] = TS(denoEdgeClient)
fileMap['deno/edge.ts'] = `
import './polyfill.js'
// @deno-types="./index.d.ts"
Expand Down Expand Up @@ -411,9 +411,7 @@ export async function generateClient(options: GenerateClientOptions): Promise<vo
}

const schemaTargetPath = path.join(outputDir, 'schema.prisma')
if (schemaPath !== schemaTargetPath) {
await fs.copyFile(schemaPath, schemaTargetPath)
}
await fs.writeFile(schemaTargetPath, datamodel, { encoding: 'utf-8' })

// copy the necessary engine files needed for the wasm/driver-adapter engine
if (
Expand Down
35 changes: 20 additions & 15 deletions packages/client/src/utils/generateInFolder.ts
@@ -1,6 +1,7 @@
import Debug from '@prisma/debug'
import { getEnginesPath } from '@prisma/engines'
import { getBinaryTargetForCurrentPlatform, getNodeAPIName } from '@prisma/get-platform'
import { getSchemaPath, type GetSchemaResult, mergeSchemas } from '@prisma/internals'
import {
ClientEngineType,
extractPreviewFeatures,
Expand Down Expand Up @@ -41,14 +42,26 @@ export async function generateInFolder({
throw new Error(`Path ${projectDir} does not exist`)
}

const schemaPath = getSchemaPath(projectDir)
const datamodel = fs.readFileSync(schemaPath, 'utf-8')
let schemaPathResult: GetSchemaResult | null = null
const schemaNotFoundError = new Error(`Could not find any schema.prisma in ${projectDir} or sub directories.`)

try {
schemaPathResult = await getSchemaPath(undefined, { cwd: projectDir })
} catch (e) {
debug('Error in getSchemaPath', e)
}

if (!schemaPathResult) {
throw schemaNotFoundError
}

const { schemas, schemaPath } = schemaPathResult

if (overrideEngineType) {
process.env.PRISMA_CLIENT_ENGINE_TYPE = overrideEngineType
}

const config = await getConfig({ datamodel, ignoreEnvVarErrors: true })
const config = await getConfig({ datamodel: schemas, ignoreEnvVarErrors: true })
const previewFeatures = extractPreviewFeatures(config)
const clientEngineType = getClientEngineType(config.generators[0])

Expand Down Expand Up @@ -94,13 +107,15 @@ export async function generateInFolder({

// TODO: use engine.getDmmf()
const dmmf = await getDMMF({
datamodel,
datamodel: schemas,
previewFeatures,
})

const schema = mergeSchemas({ schemas })

await generateClient({
binaryPaths,
datamodel,
datamodel: schema,
dmmf,
...config,
outputDir,
Expand All @@ -118,13 +133,3 @@ export async function generateInFolder({

return time
}

function getSchemaPath(projectDir: string): string {
if (fs.existsSync(path.join(projectDir, 'schema.prisma'))) {
return path.join(projectDir, 'schema.prisma')
}
if (fs.existsSync(path.join(projectDir, 'prisma/schema.prisma'))) {
return path.join(projectDir, 'prisma/schema.prisma')
}
throw new Error(`Could not find any schema.prisma in ${projectDir} or sub directories.`)
}

0 comments on commit 6fcc818

Please sign in to comment.