Skip to content

Commit

Permalink
fix(client): error on attempt to generate a package in main client di…
Browse files Browse the repository at this point in the history
…rectory (#14361)

Overriding this directory might lead to all kinds of problems. This
never supposed to work in the first place and it's already broken as of
4.0.0, we just improving error message with a fix suggestion.

Ref #13893
  • Loading branch information
SevInf committed Jul 25, 2022
1 parent cc867b2 commit 7b8616f
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/client/jest.config.js
Expand Up @@ -20,6 +20,7 @@ module.exports = {
'<rootDir>/src/__tests__/benchmarks/',
'<rootDir>/src/__tests__/types/.*/test.ts',
'<rootDir>/src/__tests__/integration/happy/exhaustive-schema/generated-dmmf.ts',
'<rootDir>/src/__tests__/generation/__fixture__',
'<rootDir>/src/__tests__/integration/happy/exhaustive-schema-mongo/generated-dmmf.ts',
'__helpers__/',
'node_modules/',
Expand Down
@@ -0,0 +1,3 @@
{
"name": "@prisma/client"
}
33 changes: 33 additions & 0 deletions packages/client/src/__tests__/generation/generator.test.ts
Expand Up @@ -40,6 +40,7 @@ describe('generator', () => {
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
dataProxy: false,
})

const manifest = omit<any, any>(generator.manifest, ['version']) as any
Expand Down Expand Up @@ -121,6 +122,7 @@ describe('generator', () => {
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
dataProxy: false,
})
} catch (e) {
expect(serializeQueryEngineName(stripAnsi(e.message))).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -171,6 +173,7 @@ describe('generator', () => {
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
dataProxy: false,
})
} catch (e) {
doesnNotExistError = e
Expand All @@ -181,6 +184,35 @@ describe('generator', () => {
}
})

test('override client package', async () => {
const generator = await getGenerator({
schemaPath: path.join(__dirname, 'main-package-override.prisma'),
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
dataProxy: false,
})

try {
await expect(generator.generate()).rejects.toThrowErrorMatchingInlineSnapshot(`
Generating client into /client/src/__tests__/generation/__fixture__/@prisma/client is not allowed.
This package is used by \`prisma generate\` and overwriting its content is dangerous.
Suggestion:
In /client/src/__tests__/generation/main-package-override.prisma replace:
8 output = "./__fixture__/@prisma/client"
with
8 output = "./__fixture__/.prisma/client"
You won't need to change your imports.
Imports from \`@prisma/client\` will be automatically forwarded to \`.prisma/client\`
`)
} finally {
generator.stop()
}
})

test('mongo', async () => {
const prismaClientTarget = path.join(__dirname, './node_modules/@prisma/client')
// Make sure, that nothing is cached.
Expand All @@ -200,6 +232,7 @@ describe('generator', () => {
baseDir: __dirname,
printDownloadProgress: false,
skipDownload: true,
dataProxy: false,
})

const manifest = omit<any, any>(generator.manifest, ['version']) as any
Expand Down
@@ -0,0 +1,14 @@
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}

generator client {
provider = "prisma-client-js"
output = "./__fixture__/@prisma/client"
}

model User {
id Int @id
name String
}
82 changes: 80 additions & 2 deletions packages/client/src/generation/generateClient.ts
Expand Up @@ -11,6 +11,7 @@ import pkgUp from 'pkg-up'
import type { O } from 'ts-toolbelt'
import { promisify } from 'util'

import { name as clientPackageName } from '../../package.json'
import type { DMMF as PrismaClientDMMF } from '../runtime/dmmf-types'
import type { Dictionary } from '../runtime/utils/common'
import { getPrismaClientDMMF } from './getDMMF'
Expand All @@ -22,6 +23,13 @@ const exists = promisify(fs.exists)
const copyFile = promisify(fs.copyFile)
const stat = promisify(fs.stat)

const GENERATED_PACKAGE_NAME = '.prisma/client'

type OutputDeclaration = {
content: string
lineNumber: number
}

export class DenylistError extends Error {
constructor(message: string) {
super(message)
Expand Down Expand Up @@ -112,7 +120,7 @@ export async function buildClient({
fileMap['index-browser.js'] = await BrowserJS(nodeTsClient)
fileMap['package.json'] = JSON.stringify(
{
name: '.prisma/client',
name: GENERATED_PACKAGE_NAME,
main: 'index.js',
types: 'index.d.ts',
browser: 'index-browser.js',
Expand Down Expand Up @@ -419,7 +427,14 @@ function validateDmmfAgainstDenylists(prismaClientDmmf: PrismaClientDMMF.Documen
* @param runtimeDirs overrides for the runtime directories
* @returns
*/
async function getGenerationDirs({ testMode, runtimeDirs, generator, outputDir }: GenerateClientOptions) {
async function getGenerationDirs({
testMode,
runtimeDirs,
generator,
outputDir,
datamodel,
schemaPath,
}: GenerateClientOptions) {
const useDefaultOutdir = testMode ? !runtimeDirs : !generator?.isCustomOutput

const _runtimeDirs = {
Expand All @@ -429,6 +444,9 @@ async function getGenerationDirs({ testMode, runtimeDirs, generator, outputDir }
}

const finalOutputDir = useDefaultOutdir ? await getDefaultOutdir(outputDir) : outputDir
if (!useDefaultOutdir) {
await verifyOutputDirectory(finalOutputDir, datamodel, schemaPath)
}

const packageRoot = await pkgUp({ cwd: path.dirname(finalOutputDir) })
const projectRoot = packageRoot ? path.dirname(packageRoot) : process.cwd()
Expand All @@ -439,3 +457,63 @@ async function getGenerationDirs({ testMode, runtimeDirs, generator, outputDir }
projectRoot,
}
}

async function verifyOutputDirectory(directory: string, datamodel: string, schemaPath: string) {
let content: string
try {
content = await fs.promises.readFile(path.join(directory, 'package.json'), 'utf8')
} catch (e) {
if (e.code === 'ENOENT') {
// no package.json exists, we are good
return
}
throw e
}
const { name } = JSON.parse(content)
if (name === clientPackageName) {
const message = [`Generating client into ${chalk.bold(directory)} is not allowed.`]
message.push('This package is used by `prisma generate` and overwriting its content is dangerous.')
message.push('')
message.push('Suggestion:')
const outputDeclaration = findOutputPathDeclaration(datamodel)

if (outputDeclaration && outputDeclaration.content.includes(clientPackageName)) {
const outputLine = outputDeclaration.content
message.push(`In ${chalk.bold(schemaPath)} replace:`)
message.push('')
message.push(
`${chalk.dim(outputDeclaration.lineNumber)} ${replacePackageName(outputLine, chalk.red(clientPackageName))}`,
)
message.push('with')

message.push(
`${chalk.dim(outputDeclaration.lineNumber)} ${replacePackageName(outputLine, chalk.green('.prisma/client'))}`,
)
} else {
message.push(
`Generate client into ${chalk.bold(replacePackageName(directory, chalk.green('.prisma/client')))} instead`,
)
}

message.push('')
message.push("You won't need to change your imports.")
message.push('Imports from `@prisma/client` will be automatically forwarded to `.prisma/client`')
const error = new Error(message.join('\n'))
throw error
}
}

function replacePackageName(directoryPath: string, replacement: string): string {
return directoryPath.replace(clientPackageName, replacement)
}

function findOutputPathDeclaration(datamodel: string): OutputDeclaration | null {
const lines = datamodel.split(/\r?\n/)

for (const [i, line] of lines.entries()) {
if (/output\s*=/.test(line)) {
return { lineNumber: i + 1, content: line.trim() }
}
}
return null
}
3 changes: 3 additions & 0 deletions packages/generator-helper/src/GeneratorProcess.ts
Expand Up @@ -18,6 +18,9 @@ export class GeneratorError extends Error {
super(message)
this.code = code
this.data = data
if (data?.stack) {
this.stack = data.stack
}
}
}

Expand Down
12 changes: 8 additions & 4 deletions packages/generator-helper/src/generatorHandler.ts
Expand Up @@ -24,8 +24,10 @@ export function generatorHandler(handler: Handler): void {
jsonrpc: '2.0',
error: {
code: -32000,
message: e.stack || e.message,
data: null,
message: e.message,
data: {
stack: e.stack,
},
},
id: json.id,
})
Expand All @@ -49,8 +51,10 @@ export function generatorHandler(handler: Handler): void {
jsonrpc: '2.0',
error: {
code: -32000,
message: e.stack || e.message,
data: null,
message: e.message,
data: {
stack: e.stack,
},
},
id: json.id,
})
Expand Down

0 comments on commit 7b8616f

Please sign in to comment.