Skip to content
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

fix: ncc support #113

Merged
merged 7 commits into from
Aug 24, 2021
Merged
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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ Official Prisma plugin for Nexus.
- [Project relation with custom resolver logic](#project-relation-with-custom-resolver-logic)
- [Supply custom custom scalars to your GraphQL schema](#supply-custom-custom-scalars-to-your-graphql-schema)
- [Notes](#notes)
- [Working with Bundlers](#working-with-bundlers)
- [Disable Peer Dependency Check](#disable-peer-dependency-check)
- [General Support](#general-support)
- [For users of `nexus-prisma@=<0.20`](#for-users-of-nexus-prisma020)
- [For users of `prisma@=<2.17`](#for-users-of-prisma217)
- [For users of `nexus@=<1.0`](#for-users-of-nexus10)
Expand Down Expand Up @@ -809,7 +812,7 @@ When your project is in a state where the generated Nexus Prisma part is missing

When `nexus-prisma` is imported it will validate that your project has peer dependencies setup correctly.

If a peer dependency is not installed it `nexus-prisma` will log an error and then exit 1. If its version does not satify the range supported by the current version of `nexus-prisma` that you have installed, then a warning will be logged. If you want to opt-out of this validation then set an envar as follows:
If a peer dependency is not installed it `nexus-prisma` will log an error and then exit 1. If its version does not satify the range supported by the current version of `nexus-prisma` that you have installed, then a warning will be logged. If you want to opt-out of this validation (e.g. you're [using a bundler](#Disable-Peer-Dependency-Check)) then set an envar as follows:

```
NO_PEER_DEPENDENCY_CHECK=true|1
Expand Down Expand Up @@ -887,6 +890,18 @@ makeSchema({

## Notes

### Working with Bundlers

#### Disable Peer Dependency Check

When working with bundlers, it probably makes sense to disable the rutnime peer dependency check system since the bundle step is merging the dependency tree into a single file and may be moved to run standalone away from the original project manifest (e.g. in a docker container).

Instructions to do this can be found [here](#Peer-Dependency-Validation).

#### General Support

`nexus-prisma` has tests showing that it supports `ncc`. Other bundlers are not tested and may or may not work. It is our goal however that nexus-prisma not be the reason for any popular bundler to not work on your project. So if you encounter a problem with one (e.g. `parcel`), open an issue here and we'll fix the issue including an addition to our test suite.

### For users of `nexus-prisma@=<0.20`

Versions of `nexus-prisma` package prior to `0.20` were a completely different version of the API, and had also become deprecated at one point to migrate to `nexus-plugin-prisma` when Nexus Framework was being worked on. All of that is history.
Expand Down
6 changes: 5 additions & 1 deletion src/generator/models/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ export function createModuleSpec(gentimeSettings: Gentime.Settings): ModuleSpec

const gentimeSettings = ${JSON.stringify(gentimeSettings.data, null, 2)}

const dmmf = getPrismaClientDmmf(gentimeSettings.prismaClientImportId)
const dmmf = getPrismaClientDmmf({
require: () => require('${gentimeSettings.data.prismaClientImportId}'),
importId: gentimeSettings.prismaClientImportId,
importIdResolved: require.resolve('${gentimeSettings.data.prismaClientImportId}')
})

const models = ModelsGenerator.JS.createNexusTypeDefConfigurations(dmmf, {
runtime: Runtime.settings,
Expand Down
68 changes: 57 additions & 11 deletions src/helpers/prisma.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,94 @@
import { DMMF } from '@prisma/client/runtime'
import dedent from 'dindist'
import ono from 'ono'
import { inspect } from 'util'
import { detectProjectPackageManager, renderRunBin } from '../lib/packageManager'
import { d } from './debugNexusPrisma'
import { GITHUB_NEW_DISCUSSION_LINK } from './errorMessages'
import kleur = require('kleur')

export const getPrismaClientDmmf = (importId = '@prisma/client'): DMMF.Document => {
/**
* Given a package loader, attempt to get the Prisma Client DMMF.
*
* @remarks Only the given require function is truly import, the rest is used for better error messages.
*
* This function is designed to support working with bundlers. Specifically `ncc` has been
* tested.
*
* This function intentionally does not do the `require`/`import` itself, leaving that to
* upstream code to handle in static ways that bundlers will be able to process.
*/
export const getPrismaClientDmmf = (packageLoader: {
/**
* A function that must return the Prisma Client Package
*/
require: () => unknown
/**
* The import specifier being used (the from "..." part)
*/
importId: string
/**
* The resolved import specifier being used. This can be different than important ID in two ways:
*
* 1. NodeJS lookp algorithm
* 2. Bundlers that rewrite import paths
*/
importIdResolved: string
}): DMMF.Document => {
d('get dmmf from @prisma/client')

let maybeDmmf: DMMF.Document | undefined
let prismaClientPackage: unknown

// prettier-ignore
const printedImportId = `${kleur.yellow(packageLoader.importId)} (resolved as ${packageLoader.importIdResolved})`

try {
// We duck type check below
// eslint-disable-next-line
maybeDmmf = require(importId).dmmf
prismaClientPackage = packageLoader.require()
} catch (error) {
// prettier-ignore
throw ono(error, dedent`
Failed to get Prisma Client DMMF. An error occured while trying to import it from ${kleur.yellow(importId)}.
Failed to get Prisma Client DMMF. An error occured while trying to import it from ${printedImportId}.
`)
}

if (maybeDmmf === undefined) {
if (!(typeof prismaClientPackage === 'object' && prismaClientPackage !== null)) {
// prettier-ignore
throw new Error(dedent`
Failed to get Prisma Client DMMF. It was imported from ${kleur.yellow(importId)} but was \`undefined\`.
Failed to get Prisma Client DMMF. It was imported from ${printedImportId} but was not the expected type. Got:

${inspect(prismaClientPackage)}
`)
}

const prismaClientPackageObject = prismaClientPackage as Record<string, unknown>

// eslint-disable-next-line
if (!prismaClientPackageObject.dmmf) {
// prettier-ignore
throw new Error(dedent`
Failed to get Prisma Client DMMF. It was imported from ${printedImportId} but did not contain "dmmf" data. Got:

${inspect(prismaClientPackage)}

This usually means that you need to run Prisma Client generation. Please run ${renderRunBin(detectProjectPackageManager(), `prisma generate`)}.
If that does not solve your problem, you can get community help by opening a discussion at ${kleur.yellow(GITHUB_NEW_DISCUSSION_LINK)}.
`)
}

/** Simple duck type to sanity check we got good data at runtime. */
// Simple duck type to sanity check we got good data at runtime.

const dmmf = prismaClientPackageObject.dmmf as DMMF.Document

const dmmf = maybeDmmf
const expectedFields = ['datamodel', 'schema', 'mappings'] as const

if (expectedFields.find((fieldName) => dmmf[fieldName] && typeof dmmf[fieldName] !== 'object')) {
throw new Error(dedent`
The DMMF imported from ${importId} appears to be invalid. Missing one/all of expected fields:
The DMMF imported from ${packageLoader.importId} appears to be invalid. Missing one/all of expected fields:
`)
}

return maybeDmmf
return dmmf
}

export type PrismaScalarType =
Expand Down
131 changes: 83 additions & 48 deletions tests/__helpers__/testProject.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import execa from 'execa'
import { debug } from 'debug'
import * as Execa from 'execa'
import * as fs from 'fs-jetpack'
import { FSJetpack } from 'fs-jetpack/types'
import { GraphQLClient } from 'graphql-request'
import { merge } from 'lodash'
import { PackageJson, TsConfigJson } from 'type-fest'

const d = debug(`testProject`)

export interface TestProject {
fs: FSJetpack
info: TestProjectInfo
run(command: string, options?: execa.SyncOptions): execa.ExecaSyncReturnValue
runAsync(command: string, options?: execa.SyncOptions): execa.ExecaChildProcess
runOrThrow(command: string, options?: execa.SyncOptions): execa.ExecaSyncReturnValue
run(command: string, options?: Execa.SyncOptions): Execa.ExecaSyncReturnValue
runAsync(command: string, options?: Execa.SyncOptions): Execa.ExecaChildProcess
runOrThrow(command: string, options?: Execa.SyncOptions): Execa.ExecaSyncReturnValue
runOrThrowNpmScript(command: string, options?: Execa.SyncOptions): Execa.ExecaSyncReturnValue
client: GraphQLClient
}

Expand Down Expand Up @@ -48,74 +52,105 @@ export class TestProjectInfo {
}
}

export function setupTestProject({
packageJson,
tsconfigJson,
}: {
packageJson?: PackageJson
tsconfigJson?: TsConfigJson
} = {}): TestProject {
export function setupTestProject(
params: {
fixture?: string
files?: {
packageJson?: PackageJson
tsconfigJson?: TsConfigJson
}
} = {}
): TestProject {
const thisPackageName = `nexus-prisma`
const tpi = new TestProjectInfo()

/**
* Allow reusing a test project directory. This can be helpful when debugging things.
*/
let fs_ = tpi.isReusingEnabled ? fs.cwd(tpi.getOrSetGet().dir) : fs.tmpDir()

fs_.write(
'package.json',
merge(
{
name: 'some-test-project',
version: '1.0.0',
},
packageJson
)
)
const fs_ = tpi.isReusingEnabled ? fs.cwd(tpi.getOrSetGet().dir) : fs.tmpDir()

fs_.write(
'tsconfig.json',
merge(
{
compilerOptions: {
strict: true,
target: 'ES2018',
module: 'CommonJS',
moduleResolution: 'node',
rootDir: 'src',
outDir: 'build',
esModuleInterop: true, // for ApolloServer b/c ws dep :(
},
include: ['src'],
} as TsConfigJson,
tsconfigJson
)
)

const api: TestProject = {
const testProject: TestProject = {
fs: fs_,
info: tpi,
run(command, options) {
return execa.commandSync(command, {
// console.log(`${command} ...`)
return Execa.commandSync(command, {
reject: false,
...options,
cwd: fs_.cwd(),
})
},
runOrThrow(command, options) {
return execa.commandSync(command, {
// console.log(`${command} ...`)
return Execa.commandSync(command, {
...options,
cwd: fs_.cwd(),
})
},
runOrThrowNpmScript(command, options) {
// console.log(`${command} ...`)
return Execa.commandSync(`npm run --silent ${command}`, {
...options,
cwd: fs_.cwd(),
})
},
runAsync(command, options) {
return execa.command(command, {
// console.log(`${command} ...`)
return Execa.command(command, {
...options,
cwd: fs_.cwd(),
})
},
client: new GraphQLClient('http://localhost:4000'),
}

return api
if (params.fixture) {
testProject.fs.copy(params.fixture, testProject.fs.cwd(), {
overwrite: true,
})
} else {
testProject.fs.write(
'package.json',
merge(
{
name: 'some-test-project',
version: '1.0.0',
},
params.files?.packageJson
)
)

testProject.fs.write(
'tsconfig.json',
merge(
{
compilerOptions: {
strict: true,
target: 'ES2018',
module: 'CommonJS',
moduleResolution: 'node',
rootDir: 'src',
outDir: 'build',
esModuleInterop: true, // for ApolloServer b/c ws dep :(
},
include: ['src'],
} as TsConfigJson,
params.files?.tsconfigJson
)
)
}

if (testProject.info.isReusing) {
d(`starting project setup cleanup for reuse`)
testProject.fs.remove(`node_modules/${thisPackageName}`)
testProject.runOrThrow(`yalc add ${thisPackageName}`)
d(`done project setup cleanup for reuse`)
} else {
d(`starting project setup`)
Execa.commandSync(`yalc publish --no-scripts`, { stdio: 'inherit' })
testProject.runOrThrow(`yalc add ${thisPackageName}`, { stdio: 'inherit' })
testProject.runOrThrow(`npm install --legacy-peer-deps`, { stdio: 'inherit' })
d(`done project setup`)
}

return testProject
}
12 changes: 12 additions & 0 deletions tests/e2e/__snapshots__/ncc.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`works with ncc 1`] = `
"type Foo {
id: ID!
}

type Query {
foos: [Foo]
}
"
`;
Loading