Created
October 19, 2023 11:56
-
-
Save DanielHoffmann/a456aadb2f27880d592419dff0df0ec8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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