Skip to content

Instantly share code, notes, and snippets.

@DanielHoffmann
Created October 19, 2023 11:56
Show Gist options
  • Save DanielHoffmann/a456aadb2f27880d592419dff0df0ec8 to your computer and use it in GitHub Desktop.
Save DanielHoffmann/a456aadb2f27880d592419dff0df0ec8 to your computer and use it in GitHub Desktop.
/* eslint-env node */
/* eslint-disable import/no-nodejs-modules */
/* eslint-disable import/no-commonjs */
/* eslint-disable import/no-dynamic-require */
const glob = require('glob')
const path = require('path')
function getPackageJsons(directory) {
const packagePaths = glob.sync('**/package.json', {
cwd: directory,
ignore: '**/node_modules/**',
})
return packagePaths.map((pkgPath) => {
return require(path.join(directory, pkgPath))
})
}
const errors = []
// dependencies that should appear as "peerDependencies" in packages and as "dependencies" in apps
// a package may have them under "devDependencies" as well if it is used in the package's storybook
// all packages must use the exact same version of these dependencies to prevent multiple version of the same package from being bundled
const packagesPeerDependencies = [
{ name: 'react', versions: new Set() },
{ name: '@types/react', versions: new Set() },
{ name: 'react-dom', versions: new Set() },
{ name: '@types/react-dom', versions: new Set() },
{ name: 'react-router-dom', versions: new Set() },
{ name: 'clsx', versions: new Set() },
{ name: 'assert', versions: new Set() },
{ name: 'date-fns', versions: new Set() },
{ name: '@fortawesome/fontawesome-svg-core', versions: new Set() },
{ name: '@fortawesome/react-fontawesome', versions: new Set() },
{ name: '@fortawesome/pro-solid-svg-icons', versions: new Set() },
{ name: '@fortawesome/pro-regular-svg-icons', versions: new Set() },
{ name: '@fortawesome/pro-light-svg-icons', versions: new Set() },
{ name: '@fortawesome/pro-thin-svg-icons', versions: new Set() },
{ name: '@fortawesome/pro-duotone-svg-icons', versions: new Set() },
{ name: '@fortawesome/free-brands-svg-icons', versions: new Set() },
{ name: '@headlessui/react', versions: new Set() },
{ name: '@sentry/browser', versions: new Set() },
]
const rootPackageDependencyMessage = 'Should only be used the root package.json'
// dependencies that are not allowed to be in the packages and apps
const disallowedDependencies = [
{ name: 'react-router', message: 'should use react-router-dom instead' },
{ name: 'prettier', message: rootPackageDependencyMessage },
{ name: 'eslint', message: rootPackageDependencyMessage },
{ name: 'typescript', message: rootPackageDependencyMessage },
{ name: 'jest', message: rootPackageDependencyMessage },
{ name: '@types/jest', message: rootPackageDependencyMessage },
{ name: '@types/node', message: rootPackageDependencyMessage },
{
name: '@fortawesome/free-solid-svg-icons',
message: 'use @fortawesome/pro-solid-svg-icons instead',
},
{
name: '@fortawesome/free-regular-svg-icons',
message: 'use @fortawesome/pro-regular-svg-icons instead',
},
{
name: '@fortawesome/fontawesome-pro',
message: 'use @fortawesome/react-fontawesome instead',
},
{
name: '@fortawesome/fontawesome-free',
message: 'use @fortawesome/react-fontawesome instead',
},
]
const root = require('./package.json')
const packages = getPackageJsons(path.join(__dirname, './packages'))
const apps = getPackageJsons(path.join(__dirname, './apps'))
function getDependencies(pkgJson) {
const dependencies = pkgJson.dependencies == null ? {} : pkgJson.dependencies
const devDependencies = pkgJson.devDependencies == null ? {} : pkgJson.devDependencies
const peerDependencies = pkgJson.peerDependencies == null ? {} : pkgJson.peerDependencies
return {
dependencies,
devDependencies,
peerDependencies,
}
}
disallowedDependencies.forEach((disallowedDep) => {
const name = disallowedDep.name
apps.concat(packages).forEach((pkg) => {
const packageVersion = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]
const { dependencies, devDependencies, peerDependencies } = getDependencies(pkg)
if (
dependencies[name] != null ||
devDependencies[name] != null ||
peerDependencies[name] != null
) {
errors.push(`${pkg.name} should not be using dependency "${name}", ${disallowedDep.message}`)
}
})
})
packagesPeerDependencies.forEach((peerDep) => {
const name = peerDep.name
apps.forEach((app) => {
const { dependencies } = getDependencies(app)
if (dependencies[name] != null) {
peerDep.versions.add(dependencies[name])
}
})
packages.forEach((package) => {
const { dependencies, devDependencies, peerDependencies } = getDependencies(package)
if (dependencies[name] != null) {
errors.push(
`${package.name} should have "${name}" in "peerDependencies" and "devDependencies" instead of "dependencies"`,
)
}
if (devDependencies[name] != null) {
peerDep.versions.add(devDependencies[name])
}
if (peerDependencies[name] != null && peerDependencies[name] !== '*') {
peerDep.versions.add(peerDependencies[name])
}
})
})
packagesPeerDependencies.forEach((peerDep) => {
if (peerDep.versions.size > 1) {
errors.push(
`${peerDep.name} has multiple different versions in different package.jsons, ${JSON.stringify(
Array.from(peerDep.versions),
)}`,
)
}
})
if (errors.length > 0) {
console.error('package.json validation errors:')
errors.forEach((err) => {
console.error(err)
})
process.exit(1)
} else {
process.exit(0)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment