Skip to content

Commit

Permalink
feat!: validate that peer dep requirements are met
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Feb 19, 2021
1 parent b28217b commit 7976bf5
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 14 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@
"devDependencies": {
"@prisma-labs/prettier-config": "0.1.0",
"@types/jest": "26.0.20",
"@types/semver": "^7.3.4",
"dripip": "0.10.0",
"jest": "26.6.3",
"jest-watch-typeahead": "0.6.1",
"nexus": "^1.0.0",
"prettier": "2.2.1",
"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",
"nexus": "^1.0.0"
},
"dependencies": {
"kleur": "^4.1.4",
"semver": "^7.3.4"
}
}
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export const hello = 'world'
import { validatePeerDependencies } from './lib/package'

validatePeerDependencies({
packageJson: require('../package.json'),
})
136 changes: 136 additions & 0 deletions src/lib/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import kleur = require('kleur')
import * as Semver from 'semver'
import { PackageJson } from 'type-fest'

/**
* 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
*/
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

try {
const name = packageJson.name ?? ''
const peerDependencies = packageJson['peerDependencies'] ?? []

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

checkPeerDependencyRangeSatisfiedOrWarn({
peerDependencyName: pdName,
requireer: packageJson,
})
}
} catch (e) {
console.warn(
renderWarning(`Something went wrong while trying to validate peer dependencies:\n\n${e.stack}`)
)
}
}

export function checkPeerDependencyRangeSatisfiedOrWarn({
peerDependencyName,
requireer,
}: {
peerDependencyName: string
requireer: PackageJson
}): void {
const pdPackageJson = require(`${peerDependencyName}/package.json`) as PackageJson
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.
if (!pdVersion) {
console.warn(
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) {
console.warn(
renderWarning(
`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
}

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

console.warn(
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)
}
}

function renderError(message: string): string {
return `${kleur.red('ERROR:')} ${message}`
}

function renderWarning(message: string): string {
return `${kleur.yellow('WARNING:')} ${message}`
}

function getPackageManagerBinName(): string {
const userAgent = process.env.npm_config_user_agent || ''

const packageManagerBinName = userAgent.includes('yarn') ? 'yarn' : 'npm'
return packageManagerBinName
}

function renderPackageCommand(command: string): string {
return `${getPackageManagerBinName()} ${command}`
}

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

return false
}
29 changes: 16 additions & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -671,18 +671,6 @@
resolved "https://registry.yarnpkg.com/@prisma-labs/prettier-config/-/prettier-config-0.1.0.tgz#ef6cb5f89487974ca997516e9b39d5ccbefeeaad"
integrity sha512-P0h2y+gnIxFP2HdsTYSYHWmabGBlxyVjnUepsrRe8gAF36mxOonGsbsQmKt/Q9H9CMjrSkFoDe5F5HLi2iW5/Q==

"@prisma/client@^2.17.0":
version "2.17.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.17.0.tgz#e38462796c2e824504763416f5e3a7219d70652d"
integrity sha512-tzsBxtx9J1epOGCiBeXur1tEz81UIdWg2G/HpDmflXKcv/MJb+KCWSKSsEW49eXcvVwRgxNyxLoCO6CwvjQKcg==
dependencies:
"@prisma/engines-version" "2.17.0-35.3c463ebd78b1d21d8fdacdd27899e280cf686223"

"@prisma/engines-version@2.17.0-35.3c463ebd78b1d21d8fdacdd27899e280cf686223":
version "2.17.0-35.3c463ebd78b1d21d8fdacdd27899e280cf686223"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.17.0-35.3c463ebd78b1d21d8fdacdd27899e280cf686223.tgz#9ae6ed4467a0febff8afaf216c1bb67225f51b84"
integrity sha512-9idv5blqPUlvUPVT48eVi3T0RS/NBklUcjrla3jWyV8AYfB2BdaG/Zci7H5ajyYLnfZZHG9tBpP0LcveQCFH8A==

"@sinonjs/commons@^1.7.0":
version "1.8.2"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b"
Expand Down Expand Up @@ -796,6 +784,11 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd"
integrity sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw==

"@types/semver@^7.3.4":
version "7.3.4"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==

"@types/stack-utils@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
Expand Down Expand Up @@ -2826,6 +2819,11 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==

kleur@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d"
integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==

leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
Expand Down Expand Up @@ -3587,7 +3585,7 @@ saxes@^5.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==

semver@7.x, semver@^7.3.2:
semver@7.x, semver@^7.3.2, semver@^7.3.4:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
Expand Down Expand Up @@ -4075,6 +4073,11 @@ type-fest@^0.11.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==

type-fest@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.1.tgz#fb17fed5e8ef5c7c8b1e0733a444d16a025a8de1"
integrity sha512-IRDHjLrSaDb8NdGkqMXWjB6L/4ZhC9y2OtmheHmdkrhufHi8uRvkfJHzW7XKog8RAVZsgZ8JsZqDFg97aC99Xw==

type-fest@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
Expand Down

0 comments on commit 7976bf5

Please sign in to comment.