Skip to content

Commit

Permalink
improve: show disable guide in peer dep messages (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Aug 26, 2021
1 parent 8e61651 commit c5d7c15
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 69 deletions.
9 changes: 9 additions & 0 deletions src/helpers/utils.ts
Expand Up @@ -67,3 +67,12 @@ export function resolveNestedTilde(tildePattern: string, path: string): string {

return newPath
}

export function isModuleNotFoundError(error: unknown): error is Error {
// @ts-expect-error .code is not a standard field
if (error instanceof Error && error.code === 'MODULE_NOT_FOUND') {
return true
}

return false
}
156 changes: 98 additions & 58 deletions src/lib/peerDepValidator.ts
Expand Up @@ -2,6 +2,7 @@ import dedent from 'dindist'
import * as Semver from 'semver'
import { PackageJson } from 'type-fest'
import { d } from '../helpers/debugNexusPrisma'
import { isModuleNotFoundError } from '../helpers/utils'
import { detectProjectPackageManager, renderAddDeps } from './packageManager'
import kleur = require('kleur')

Expand All @@ -16,9 +17,30 @@ type Failure =
| { message: string; kind: 'peer_dep_package_json_read_error'; error: unknown }
| { message: string; kind: 'unexpected_error'; error: unknown }

export const envarSpecs = {
NO_PEER_DEPENDENCY_CHECK: {
name: `NO_PEER_DEPENDENCY_CHECK`,
values: ['true', '1'],
},
PEER_DEPENDENCY_CHECK: {
name: `PEER_DEPENDENCY_CHECK`,
values: ['false', '0'],
},
}

export function enforceValidPeerDependencies({ packageJson }: { packageJson: PackageJson }): void {
if (['true', '1'].includes(process.env.NO_PEER_DEPENDENCY_CHECK ?? '')) return
if (['false', '0'].includes(process.env.PEER_DEPENDENCY_CHECK ?? '')) return
if (
envarSpecs.NO_PEER_DEPENDENCY_CHECK.values.includes(
process.env[envarSpecs.NO_PEER_DEPENDENCY_CHECK.name] ?? ''
)
)
return

if (
envarSpecs.PEER_DEPENDENCY_CHECK.values.includes(process.env[envarSpecs.PEER_DEPENDENCY_CHECK.name] ?? '')
)
return

d('validating peer dependencies')

const failure = validatePeerDependencies({ packageJson })
Expand Down Expand Up @@ -57,9 +79,16 @@ export function validatePeerDependencies({ packageJson }: { packageJson: Package
if (failure) return failure
}
} catch (error: unknown) {
const code = `unexpected_error`
return {
kind: 'unexpected_error',
message: renderWarning(`Something went wrong while trying to validate peer dependencies`),
kind: code,
message: renderWarning({
title: `Something went wrong while trying to validate peer dependencies`,
code,
reason: error instanceof Error ? error.message : String(error),
consequence: `There seems to be a bug so the regular correctness checks of the peer dep checker cannot be carried out now. You are on your own.`,
solution: `Please report this issue.`,
}),
error,
}
}
Expand Down Expand Up @@ -91,14 +120,18 @@ export function validatePeerDependencyRangeSatisfied({
}
}

const code = 'peer_dep_not_installed'
return {
kind: 'peer_dep_not_installed',
message: renderError(
kind: code,
message: renderError({
title: `Peer dependency validation check failed.`,
// prettier-ignore
dedent`
${kleur.green(peerDependencyName)} is a peer dependency required by ${renderPackageJsonField(requireer,'name')}. But you have not installed it into this project yet. Please run \`${kleur.green(renderAddDeps(detectProjectPackageManager(),[peerDependencyName]))}\`.
`
),
reason: dedent`${kleur.green(peerDependencyName)} is a peer dependency required by ${renderPackageJsonField(requireer,'name')}. But you have not installed it into this project yet.`,
code,
// prettier-ignore
solution: `Please run \`${kleur.green(renderAddDeps(detectProjectPackageManager(),[peerDependencyName]))}\`.`,
consequence: `Your project may not work correctly.`,
}),
}
}

Expand All @@ -108,37 +141,29 @@ export function validatePeerDependencyRangeSatisfied({
// npm enforces that package manifests have a valid "version" field so this
// case _should_ never happen under normal circumstances.
if (!pdVersion) {
const code = 'peer_dep_package_json_invalid'
return {
kind: 'peer_dep_package_json_invalid',
message: renderWarning(
`Peer dependency validation check failed unexpectedly. ${renderPackageJsonField(
requireer,
'name'
)} requires peer dependency ${renderPackageJsonField(
pdPackageJson,
'name'
)}. No version info for ${renderPackageJsonField(
pdPackageJson,
'name'
)} could be found in its package.json thus preventing a check if its version satisfies the peer dependency version range.`
),
kind: code,
message: renderWarning({
title: `Peer dependency validation check failed unexpectedly.`,
// prettier-ignore
reason: `${renderPackageJsonField(requireer, 'name')} requires peer dependency ${renderPackageJsonField(pdPackageJson, 'name')}. No version info for ${renderPackageJsonField(pdPackageJson, 'name')} could be found in its package.json thus preventing a check if its version satisfies the peer dependency version range.`,
consequence: `Peer dep validator checks cannot be carried out so you are on your own.`,
code,
}),
}
}

if (!pdVersionRangeSupported) {
const code = `unknown`
console.warn(
renderWarning(
`Peer dependency validation check failed unexpectedly. ${renderPackageJsonField(
requireer,
'name'
)} apparently requires peer dependency ${renderPackageJsonField(
pdPackageJson,
'name'
)} yet ${renderPackageJsonField(
pdPackageJson,
'name'
)} is not listed in the peer dependency listing of ${renderPackageJsonField(requireer, 'name')}.`
)
renderWarning({
title: `Peer dependency validation check failed unexpectedly.`,
// prettier-ignore
reason: `${renderPackageJsonField(requireer, 'name')} apparently requires peer dependency ${renderPackageJsonField(pdPackageJson, 'name')} yet ${renderPackageJsonField(pdPackageJson, 'name')} is not listed in the peer dependency listing of ${renderPackageJsonField(requireer, 'name')}.`,
consequence: `There seems to be a bug so the regular correctness checks of the peer dep checker cannot be carried out now. You are on your own. Please report this issue.`,
code,
})
)
return null
}
Expand All @@ -149,36 +174,51 @@ export function validatePeerDependencyRangeSatisfied({

return {
kind: 'peer_dep_invalid_version',
message: renderWarning(
`Peer dependency validation check failed: ${renderPackageJsonField(
requireer,
'name'
)}@${renderPackageJsonField(requireer, 'version')} does not officially support ${renderPackageJsonField(
pdPackageJson,
'name'
)}@${renderPackageJsonField(
pdPackageJson,
'version'
)}. The officially supported range is: \`${pdVersionRangeSupported}\`. This could lead to undefined behaviors and bugs.`
),
message: renderWarning({
title: `Peer dependency validation check failed`,
// prettier-ignore
reason: `${renderPackageJsonField(requireer, 'name')}@${renderPackageJsonField(requireer, 'version')} does not officially support ${renderPackageJsonField(pdPackageJson, 'name')}@${renderPackageJsonField(pdPackageJson, 'version')}. The officially supported range is: \`${pdVersionRangeSupported}\`.`,
consequence: `This could lead to undefined behaviors and bugs.`,
code: `peer_dep_invalid_version`,
}),
}
}

function renderError(message: string): string {
return `${kleur.red('ERROR:')} ${message}`
//prettier-ignore
const prettyPrintedDisableGuide = dedent`
HOW TO DISABLE:
You can disable this peer dependency check by setting one of two environment variables. Their specs are:
${envarSpecs.NO_PEER_DEPENDENCY_CHECK.name} = ${envarSpecs.NO_PEER_DEPENDENCY_CHECK.values.map(_=>`'${_}'`).join(` | `)}
${envarSpecs.PEER_DEPENDENCY_CHECK.name} = ${envarSpecs.PEER_DEPENDENCY_CHECK.values.map(_=>`'${_}'`).join(` | `)}
Examples:
NO_PEER_DEPENDENCY_CHECK='true'
NO_PEER_DEPENDENCY_CHECK='1'
PEER_DEPENDENCY_CHECK='false'
PEER_DEPENDENCY_CHECK='0'
`

type DiagnosticInfo = {
title: string
code: string
reason: string
consequence: string
solution?: string
}

function renderWarning(message: string): string {
return `${kleur.yellow('WARNING:')} ${message}`
function renderError(params: DiagnosticInfo): string {
const solution = params.solution ? `\n\nSOLUTION: ${params.solution}` : ''
// prettier-ignore
return `${kleur.red('ERROR:')} ${params.title}\n\nREASON: ${params.reason}\n\nCONSEQUENCE: ${params.consequence}${solution}\n\n${prettyPrintedDisableGuide}\n\nCODE: ${params.code}`
}

function isModuleNotFoundError(error: unknown): error is Error {
// @ts-expect-error .code is not a standard field
if (error instanceof Error && error.code === 'MODULE_NOT_FOUND') {
return true
}

return false
function renderWarning(params: DiagnosticInfo): string {
const solution = params.solution ? `\n\nSOLUTION: ${params.solution}` : ''
// prettier-ignore
return `${kleur.yellow('WARNING:')} ${params.title}\n\nREASON: ${params.reason}\n\nCONSEQUENCE: ${params.consequence}${solution}\n\n${prettyPrintedDisableGuide}\n\nCODE: ${params.code}`
}

function renderPackageJsonField(packageJson: PackageJson, fieldName: keyof PackageJson): string {
Expand Down
3 changes: 2 additions & 1 deletion tests/e2e/e2e.test.ts
Expand Up @@ -5,6 +5,7 @@ import { gql } from 'graphql-request'
import * as GQLScalars from 'graphql-scalars'
import stripAnsi from 'strip-ansi'
import { inspect } from 'util'
import { envarSpecs } from '../../src/lib/peerDepValidator'
import { assertBuildPresent } from '../__helpers__/helpers'
import { createPrismaSchema } from '../__helpers__/testers'
import { setupTestProject, TestProject } from '../__helpers__/testProject'
Expand Down Expand Up @@ -281,7 +282,7 @@ it('When bundled custom scalars are used the project type checks and generates e
DB_URL="postgres://bcnfshogmxsukp:e31b6ddc8b9d85f8964b6671e4b578c58f0d13e15f637513207d44268eabc950@ec2-54-196-33-23.compute-1.amazonaws.com:5432/d17vadgam0dtao?schema=${
process.env.E2E_DB_SCHEMA ?? 'local'
}"
NO_PEER_DEPENDENCY_CHECK="true"
${envarSpecs.NO_PEER_DEPENDENCY_CHECK.name}="true"
`,
},
]
Expand Down
94 changes: 90 additions & 4 deletions tests/lib/__snapshots__/peerDepValidator.test.ts.snap
Expand Up @@ -3,14 +3,56 @@
exports[`ValidatePeerDependencies if peer dep missing, then returns failure 1`] = `
"{
kind: 'peer_dep_not_installed',
message: 'ERROR: charlie is a peer dependency required by alpha. But you have not installed it into this project yet. Please run \`yarn add charlie\`.'
message: 'ERROR: Peer dependency validation check failed.\\\\n' +
'\\\\n' +
'REASON: charlie is a peer dependency required by alpha. But you have not installed it into this project yet.\\\\n' +
'\\\\n' +
'CONSEQUENCE: Your project may not work correctly.\\\\n' +
'\\\\n' +
'SOLUTION: Please run \`yarn add charlie\`.\\\\n' +
'\\\\n' +
'HOW TO DISABLE:\\\\n' +
'\\\\n' +
' You can disable this peer dependency check by setting one of two environment variables. Their specs are:\\\\n' +
'\\\\n' +
\\" NO_PEER_DEPENDENCY_CHECK = 'true' | '1'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK = 'false' | '0'\\\\n\\" +
'\\\\n' +
' Examples:\\\\n' +
'\\\\n' +
\\" NO_PEER_DEPENDENCY_CHECK='true'\\\\n\\" +
\\" NO_PEER_DEPENDENCY_CHECK='1'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK='false'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK='0'\\\\n\\" +
'\\\\n' +
'CODE: peer_dep_not_installed'
}"
`;

exports[`ValidatePeerDependencies if peer dep package.json missing version field, then returns failure 1`] = `
"{
kind: 'peer_dep_package_json_invalid',
message: 'WARNING: Peer dependency validation check failed unexpectedly. alpha requires peer dependency charlie. No version info for charlie could be found in its package.json thus preventing a check if its version satisfies the peer dependency version range.'
message: 'WARNING: Peer dependency validation check failed unexpectedly.\\\\n' +
'\\\\n' +
'REASON: alpha requires peer dependency charlie. No version info for charlie could be found in its package.json thus preventing a check if its version satisfies the peer dependency version range.\\\\n' +
'\\\\n' +
'CONSEQUENCE: Peer dep validator checks cannot be carried out so you are on your own.\\\\n' +
'\\\\n' +
'HOW TO DISABLE:\\\\n' +
'\\\\n' +
' You can disable this peer dependency check by setting one of two environment variables. Their specs are:\\\\n' +
'\\\\n' +
\\" NO_PEER_DEPENDENCY_CHECK = 'true' | '1'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK = 'false' | '0'\\\\n\\" +
'\\\\n' +
' Examples:\\\\n' +
'\\\\n' +
\\" NO_PEER_DEPENDENCY_CHECK='true'\\\\n\\" +
\\" NO_PEER_DEPENDENCY_CHECK='1'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK='false'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK='0'\\\\n\\" +
'\\\\n' +
'CODE: peer_dep_package_json_invalid'
}"
`;

Expand All @@ -19,8 +61,52 @@ exports[`ValidatePeerDependencies if peer dep version satisfies required range,
exports[`ValidatePeerDependencies if project peer dep version does not satisfy required range, then returns failure 1`] = `
"{
kind: 'peer_dep_invalid_version',
message: 'WARNING: Peer dependency validation check failed: alpha@1.0.0 does not officially support charlie@1.0.0. The officially supported range is: \`2.0.x\`. This could lead to undefined behaviors and bugs.'
message: 'WARNING: Peer dependency validation check failed\\\\n' +
'\\\\n' +
'REASON: alpha@1.0.0 does not officially support charlie@1.0.0. The officially supported range is: \`2.0.x\`.\\\\n' +
'\\\\n' +
'CONSEQUENCE: This could lead to undefined behaviors and bugs.\\\\n' +
'\\\\n' +
'HOW TO DISABLE:\\\\n' +
'\\\\n' +
' You can disable this peer dependency check by setting one of two environment variables. Their specs are:\\\\n' +
'\\\\n' +
\\" NO_PEER_DEPENDENCY_CHECK = 'true' | '1'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK = 'false' | '0'\\\\n\\" +
'\\\\n' +
' Examples:\\\\n' +
'\\\\n' +
\\" NO_PEER_DEPENDENCY_CHECK='true'\\\\n\\" +
\\" NO_PEER_DEPENDENCY_CHECK='1'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK='false'\\\\n\\" +
\\" PEER_DEPENDENCY_CHECK='0'\\\\n\\" +
'\\\\n' +
'CODE: peer_dep_invalid_version'
}"
`;

exports[`enforceValidPeerDependencies if peer dependency is missing, than logs and process exits 1 1`] = `"ERROR: charlie is a peer dependency required by alpha. But you have not installed it into this project yet. Please run \`yarn add charlie\`."`;
exports[`enforceValidPeerDependencies if peer dependency is missing, than logs and process exits 1 1`] = `
"ERROR: Peer dependency validation check failed.
REASON: charlie is a peer dependency required by alpha. But you have not installed it into this project yet.
CONSEQUENCE: Your project may not work correctly.
SOLUTION: Please run \`yarn add charlie\`.
HOW TO DISABLE:
You can disable this peer dependency check by setting one of two environment variables. Their specs are:
NO_PEER_DEPENDENCY_CHECK = 'true' | '1'
PEER_DEPENDENCY_CHECK = 'false' | '0'
Examples:
NO_PEER_DEPENDENCY_CHECK='true'
NO_PEER_DEPENDENCY_CHECK='1'
PEER_DEPENDENCY_CHECK='false'
PEER_DEPENDENCY_CHECK='0'
CODE: peer_dep_not_installed"
`;
17 changes: 11 additions & 6 deletions tests/lib/peerDepValidator.test.ts
Expand Up @@ -2,6 +2,7 @@ import dedent from 'dindist'
import * as Execa from 'execa'
import { merge, omit } from 'lodash'
import { PackageJson } from 'type-fest'
import { envarSpecs } from '../../src/lib/peerDepValidator'
import { assertBuildPresent } from '../__helpers__/helpers'
import { setupTestProject, TestProject } from '../__helpers__/testProject'

Expand Down Expand Up @@ -170,14 +171,18 @@ describe('ValidatePeerDependencies', () => {
})

describe('enforceValidPeerDependencies', () => {
it('if PEER_DEPENDENCY_CHECK=false|0 then no validation happens', () => {
expect(runEnforceValidPeerDependencies({ env: { PEER_DEPENDENCY_CHECK: 'false' } }).stdout).toEqual(``)
expect(runEnforceValidPeerDependencies({ env: { PEER_DEPENDENCY_CHECK: '0' } }).stdout).toEqual(``)
it(`if ${[envarSpecs.PEER_DEPENDENCY_CHECK.name]}=false|0 then no validation happens`, () => {
// prettier-ignore
expect(runEnforceValidPeerDependencies({ env: { [envarSpecs.PEER_DEPENDENCY_CHECK.name]: 'false' } }).stdout).toEqual(``)
// prettier-ignore
expect(runEnforceValidPeerDependencies({ env: { [envarSpecs.PEER_DEPENDENCY_CHECK.name]: '0' } }).stdout).toEqual(``)
})

it('if NO_PEER_DEPENDENCY_CHECK=true|1 then no validation happens', () => {
expect(runEnforceValidPeerDependencies({ env: { NO_PEER_DEPENDENCY_CHECK: 'true' } }).stdout).toEqual(``)
expect(runEnforceValidPeerDependencies({ env: { NO_PEER_DEPENDENCY_CHECK: '1' } }).stdout).toEqual(``)
it(`if ${[envarSpecs.NO_PEER_DEPENDENCY_CHECK.name]}=true|1 then no validation happens`, () => {
// prettier-ignore
expect(runEnforceValidPeerDependencies({ env: { [envarSpecs.NO_PEER_DEPENDENCY_CHECK.name]: 'true' } }).stdout).toEqual(``)
// prettier-ignore
expect(runEnforceValidPeerDependencies({ env: { [envarSpecs.NO_PEER_DEPENDENCY_CHECK.name]: '1' } }).stdout).toEqual(``)
})

it('if peer dependency is missing, than logs and process exits 1', () => {
Expand Down

0 comments on commit c5d7c15

Please sign in to comment.