From c5d7c15006781865de40c428dc77da3c085063ce Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Thu, 26 Aug 2021 12:13:32 -0400 Subject: [PATCH] improve: show disable guide in peer dep messages (#118) --- src/helpers/utils.ts | 9 + src/lib/peerDepValidator.ts | 156 +++++++++++------- tests/e2e/e2e.test.ts | 3 +- .../peerDepValidator.test.ts.snap | 94 ++++++++++- tests/lib/peerDepValidator.test.ts | 17 +- 5 files changed, 210 insertions(+), 69 deletions(-) diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index 4df399608..926009069 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -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 +} diff --git a/src/lib/peerDepValidator.ts b/src/lib/peerDepValidator.ts index 696066908..5f2048bf3 100644 --- a/src/lib/peerDepValidator.ts +++ b/src/lib/peerDepValidator.ts @@ -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') @@ -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 }) @@ -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, } } @@ -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.`, + }), } } @@ -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 } @@ -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 { diff --git a/tests/e2e/e2e.test.ts b/tests/e2e/e2e.test.ts index a6c2f81d9..1554430e5 100644 --- a/tests/e2e/e2e.test.ts +++ b/tests/e2e/e2e.test.ts @@ -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' @@ -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" `, }, ] diff --git a/tests/lib/__snapshots__/peerDepValidator.test.ts.snap b/tests/lib/__snapshots__/peerDepValidator.test.ts.snap index 6adc408a5..f5074ffb9 100644 --- a/tests/lib/__snapshots__/peerDepValidator.test.ts.snap +++ b/tests/lib/__snapshots__/peerDepValidator.test.ts.snap @@ -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' }" `; @@ -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" +`; diff --git a/tests/lib/peerDepValidator.test.ts b/tests/lib/peerDepValidator.test.ts index 6d60ea6d3..17fa9a247 100644 --- a/tests/lib/peerDepValidator.test.ts +++ b/tests/lib/peerDepValidator.test.ts @@ -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' @@ -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', () => {