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

tests(lib): peer dep validator #3

Merged
merged 7 commits into from
Feb 20, 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
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