Skip to content

Commit

Permalink
tests(lib): peer dep validator (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason Kuhrt authored Feb 20, 2021
1 parent 4992741 commit 2173bf0
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 78 deletions.
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Tests

- We disable `kleur` colors so snapshots do not have them. It would be nice to put the DX of colors into tests but needs some work. Node 12/14 results in different codes, [thus different snapshots](https://github.com/prisma/nexus-prisma/pull/3#issuecomment-782432471). See test-mode feature request here: https://github.com/lukeed/kleur/issues/47#issue-812419257.
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dev": "tsc --build --watch",
"build": "yarn clean && tsc",
"test": "jest",
"tdd": "jest --watch",
"clean": "rm -rf dist && rm -rf node_modules/.cache",
"release:pr": "dripip pr",
"release:canary": "dripip preview",
Expand All @@ -21,24 +22,31 @@
},
"devDependencies": {
"@prisma-labs/prettier-config": "0.1.0",
"@prisma/client": "2.17.0",
"@types/jest": "26.0.20",
"@types/lodash": "^4.14.168",
"@types/semver": "^7.3.4",
"dripip": "0.10.0",
"execa": "^5.0.0",
"fs-jetpack": "^4.1.0",
"jest": "26.6.3",
"jest-watch-typeahead": "0.6.1",
"lodash": "^4.17.20",
"nexus": "^1.0.0",
"prettier": "2.2.1",
"prisma": "2.17.0",
"ts-jest": "26.5.1",
"ts-node": "^9.1.1",
"type-fest": "^0.21.1",
"typescript": "4.1.5"
},
"prettier": "@prisma-labs/prettier-config",
"peerDependencies": {
"@prisma/client": "^2.17.0",
"@prisma/client": "2.17.x",
"nexus": "^1.0.0"
},
"dependencies": {
"endent": "^2.0.1",
"kleur": "^4.1.4",
"semver": "^7.3.4"
}
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { validatePeerDependencies } from './lib/package'
import { enforceValidPeerDependencies } from './lib/peerDepValidator'

validatePeerDependencies({
packageJson: require('../package.json'),
enforceValidPeerDependencies({
packageJson: require('../../package.json'),
})

const nexusPrisma = 'todo'
Expand Down
149 changes: 84 additions & 65 deletions src/lib/package.ts → src/lib/peerDepValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,108 @@ import kleur = require('kleur')
import * as Semver from 'semver'
import { PackageJson } from 'type-fest'

type Failure =
| { message: string; kind: 'peer_dep_not_installed' }
| { message: string; kind: 'peer_dep_invalid_json'; error: unknown }
| { message: string; kind: 'peer_dep_invalid_package_json' }
| { message: string; kind: 'peer_dep_invalid_version' }
| { message: string; kind: 'unexpected_error'; error: unknown }

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

const failure = validatePeerDependencies({ packageJson })
console.log(failure)

if (failure) {
console.log(failure.message)

if ('error' in failure) {
console.error(failure.error)
}

if (failure.kind === 'peer_dep_not_installed') {
process.exit(1)
}
}
}

/**
* Check that the given package's peer dependency requirements are met.
*
* NO-op if PEER_DEPENDENCY_CHECK envar is set to false or 0
* NO-op if NO_PEER_DEPENDENCY_CHECK envar is set to true or 1
* When envar skipping enabled then:
*
* 1. NO-op if PEER_DEPENDENCY_CHECK envar is set to false or 0
* 2. NO-op if NO_PEER_DEPENDENCY_CHECK envar is set to true or 1
*/
export function validatePeerDependencies({ 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

export function validatePeerDependencies({ packageJson }: { packageJson: PackageJson }): null | Failure {
try {
const name = packageJson.name ?? ''
const peerDependencies = packageJson['peerDependencies'] ?? []

for (const [pdName, _] of Object.entries(peerDependencies)) {
checkPeerDependencyIsImportableOrFatal({ requiredBy: name, dependencyName: pdName })

checkPeerDependencyRangeSatisfiedOrWarn({
const failure = validatePeerDependencyRangeSatisfied({
peerDependencyName: pdName,
requireer: packageJson,
})

if (failure) return failure
}
} catch (error) {
return {
kind: 'unexpected_error',
message: renderWarning(`Something went wrong while trying to validate peer dependencies`),
error,
}
} catch (e) {
console.warn(
renderWarning(`Something went wrong while trying to validate peer dependencies:\n\n${e.stack}`)
)
}

return null
}

export function checkPeerDependencyRangeSatisfiedOrWarn({
export function validatePeerDependencyRangeSatisfied({
peerDependencyName,
requireer,
}: {
peerDependencyName: string
requireer: PackageJson
}): void {
const pdPackageJson = require(`${peerDependencyName}/package.json`) as PackageJson
}): null | Failure {
let pdPackageJson: PackageJson

try {
pdPackageJson = require(`${peerDependencyName}/package.json`) as PackageJson
} catch (error: unknown) {
if (!isModuleNotFoundError(error)) {
return {
kind: 'peer_dep_invalid_json',
message: `Peer dependency check found ${peerDependencyName} requried by ${requireer.name} to be installed but encountered an error while reading its package.json.`,
error,
}
}

return {
kind: 'peer_dep_not_installed',
message: renderError(
`${kleur.green(peerDependencyName)} is a peer dependency required by ${kleur.yellow(
requireer.name ?? ''
)}. But you have not installed it into this project yet. Please run \`${kleur.green(
renderPackageCommand(`add ${peerDependencyName}`)
)}\`.`
),
}
}

const pdVersion = pdPackageJson.version
const pdVersionRangeSupported = requireer.peerDependencies?.[peerDependencyName]

// npm enforces that package manifests have a valid "version" field so this case _should_ never happen under normal circumstances.
// npm enforces that package manifests have a valid "version" field so this
// case _should_ never happen under normal circumstances.
if (!pdVersion) {
console.warn(
renderWarning(
return {
kind: 'peer_dep_invalid_package_json',
message: renderWarning(
`Peer dependency validation check failed unexpectedly. ${requireer.name} requires peer dependency ${pdPackageJson.name}. No version info for ${pdPackageJson.name} could be found in its package.json thus preventing a check if its version satisfies the peer dependency version range.`
)
)
return
),
}
}

if (!pdVersionRangeSupported) {
Expand All @@ -58,53 +112,18 @@ export function checkPeerDependencyRangeSatisfiedOrWarn({
`Peer dependency validation check failed unexpectedly. ${requireer.name} apparently requires peer dependency ${pdPackageJson.name} yet ${pdPackageJson.name} is not listed in the peer dependency listing of ${requireer.name}.`
)
)
return
return null
}

if (Semver.satisfies(pdVersion, pdVersionRangeSupported)) {
return
return null
}

console.warn(
renderWarning(
return {
kind: 'peer_dep_invalid_version',
message: renderWarning(
`Peer dependency validation check failed: ${requireer.name}@${requireer.version} does not officially support ${pdPackageJson.name}@${pdPackageJson.version}. The officially supported range is: \`${pdVersionRangeSupported}\`. This could lead to undefined behaviors and bugs.`
)
)
}

/**
* Ensure that some package has been installed as a peer dep by the user.
*/
export function checkPeerDependencyIsImportableOrFatal({
dependencyName,
requiredBy,
}: {
dependencyName: string
requiredBy: string
}): void {
try {
require(dependencyName)
} catch (error: unknown) {
if (!isModuleNotFoundError(error)) {
console.warn(
`Peer dependency check confirmed that ${dependencyName} requried by ${requiredBy} is importable however an error occured during import. This probably means something is wrong and your application will not work.\n\n${
error instanceof Error ? error.stack : error
}`
)
return
}

console.error(
renderError(
`${kleur.green(dependencyName)} is a peer dependency required by ${kleur.yellow(
requiredBy
)}. But you have not installed it into this project yet. Please run \`${kleur.green(
renderPackageCommand(`add ${dependencyName}`)
)}\`.`
)
)

process.exit(1)
),
}
}

Expand All @@ -128,7 +147,7 @@ function renderPackageCommand(command: string): string {
}

function isModuleNotFoundError(error: any): error is Error {
if (error instanceof Error && (error as any).code !== 'MODULE_NOT_FOUND') {
if (error instanceof Error && (error as any).code === 'MODULE_NOT_FOUND') {
return true
}

Expand Down
32 changes: 32 additions & 0 deletions tests/lib/__snapshots__/peerDepValidator.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

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\`.'
}"
`;

exports[`ValidatePeerDependencies if peer dep package.json missing version field, then returns failure 1`] = `
"{
kind: 'peer_dep_invalid_package_json',
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.'
}"
`;

exports[`ValidatePeerDependencies if peer dep version satisfies required range, then returns null 1`] = `"null"`;

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.'
}"
`;

exports[`enforceValidPeerDependencies if peer dependency is missing, than logs and process exits 1 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\`.'
}
ERROR: charlie is a peer dependency required by alpha. But you have not installed it into this project yet. Please run \`yarn add charlie\`."
`;
Loading

0 comments on commit 2173bf0

Please sign in to comment.