Manypkg is a linter for package.json
files in Yarn, Bolt or pnpm monorepos.
yarn add @manypkg/cli
manypkg check
manypkg check
runs all of the checks against your repo, logs any errors and exits with a code
manypkg fix
manypkg fix
runs all of the checks against your repo and fixes any of problems that can be fixed.
manypkg run <partial package name or directory> <script>
manypkg run
executes scripts for packages within a monorepo.
As an example, let's say there are two packages: @project/package-a
at packages/pkg-a
and @project/package-b
at packages/pkg-b
which both have a start
script, manypkg run
can be used like this:
yarn manypkg run pkg-a start
yarn manypkg run a start
yarn manypkg run package-a start
yarn manypkg run @project/package-a start
yarn manypkg run packages/pkg-a start
yarn manypkg run package-b start
yarn manypkg run b start
The following wouldn't work though because the package
and pkg
aren't unique among the package names/directories:
yarn manypkg run package start
yarn manypkg run pkg start
In order to target a package with a name that is a substring of another (for example, @project/package-a
at packages/pkg-a
and @project/package-a-1
at packages/pkg-a-1
), use the exact package or directory name:
yarn manypkg run @project/package-a start
yarn manypkg run packages/pkg-a start
manypkg exec <cli command>
manypkg exec
executes a command for all packages within a monorepo.
As an example, let's say there are two packages which both have a dist
dir, manypkg exec
can be used like this:
yarn manypkg exec rm -rf dist
Manypkg supports a number of options. Options can be provided using the manypkg
key in your root package.json
file:
{
"name": "monorepo-root",
"private": true,
"manypkg": {}
}
defaultBranch
Used by the Incorrect repository
field rule to determine the correct name for repositories. The default value is master
.
{
"manypkg": {
"defaultBranch": "master"
}
}
ignoredRules
Used to determine which checks/fixes should ignored by the Manypkg cli. The default value is []
(all checks/fixes enabled).
{
"manypkg": {
"ignoredRules": []
}
}
To ignore a rule, find the rule in the Checks section below and add its "Key" value to the array. For example, to disable the External mismatch rule:
{
"manypkg": {
"ignoredRules": ["EXTERNAL_MISMATCH"]
}
}
workspaceProtocol
Used to determine whether the workspace:
protocol for internal packages is required (require
) or allowed (allow
). The default value is allow
.
{
"manypkg": {
"workspaceProtocol": "allow"
}
}
private: true
/is not published. It does not refer to a package published to a private registry here.Key: EXTERNAL_MISMATCH
The ranges for all dependencies(excluding peerDependencies
) on external packages should exactly match(===
). It's important to note that this check does not enforce that only a single version of an external package is installed, only that two versions of an external package will never be installed because they're specified as dependencies of internal packages.
So that only a single version of an external package will be installed because having multiple versions of the same package can cause confusion and bundle size problems especially with libraries like React that require there to only be a single copy of the library.
The most commonly used range of the dependency is set as the range at every non-peer dependency place it is depended on. If for some reason, every range is used the same amount of times, they'll all be fixed to the highest version.
There are some cases where you might want to intentionally have conflicts between versions. To do this, you can use something that isn't a valid semver range instead of a range such as a git url or etc. If you'd like a conflicting version of an npm package, you can use npm:pkg-name@your-range-here
instead of just a range and it will be ignored.
Note: Do this with care, having different versions of the same package can lead to strange bugs
Key: INTERNAL_MISMATCH
The ranges for all regular dependencies, devDependencies and optionalDependencies(not peerDependencies) on internal packages should include the version of the internal package.
So that an internal package that depends on another internal package will always get the local version of the internal package rather than a version from the registry because installing internal packages from the registry can be very confusing since you generally expect to get the local version when you depend on an internal package.
If the range is a caret range or a tilde range with no other comparators, the range is set as a caret or tilde range respectively with the version of the internal package. If it is any other range, the range is set to the exact version of the internal package.
Key: INVALID_DEV_AND_PEER_DEPENDENCY_RELATIONSHIP
All peerDependencies
should also be specified in devDependencies
and the range specified in devDependencies
should be a subset of the range for that dependency in peerDependencies
.
This is so that peerDependencies
are available in the package during development for testing and etc.
The range for the dependency specified in peerDependencies
is added to devDependencies
unless the package is already a non-peer dependency elsewhere in the repo in which, that range is used instead.
Key: ROOT_HAS_PROD_DEPENDENCIES
The root package should not have any production dependencies
, instead all dependencies should be in devDependencies
.
The root package.json
of a monorepo is not published so whether a dependency is in devDependencies
or dependencies
does not make a difference and having one place to put dependencies in the root means that people do not have to arbitrarily decide where a dependency should go every time they install one. We prefer devDependencies
because a monorepo root should contain only tooling dependencies.
All devDependencies
in the root package.json
are moved to dependencies
.
Key: MULTIPLE_DEPENDENCY_TYPES
A dependency shouldn't be specified in more than one of dependencies
, devDependencies
or optionalDependencies
.
The dep is removed from devDependencies
or optionalDependencies
if it's also in dependencies
, if it's in devDependencies
and optionalDependencies
, it is removed from dependencies
.
Key: INVALID_PACKAGE_NAME
There are rules from npm about what a package name can be and a package will fail to publish if those rules are not met.
All packages will be published together so some packages may depend on a package which can't be published. Checking for invalid package names prevents this kind of publish failure.
This requires manual fixing as automatically fixing this may lead to valid but incorrect package names.
Key: UNSORTED_DEPENDENCIES
Dependencies in the dependency fields(dependencies
, devDependencies
, peerDependencies
, optionalDependencies
) should be sorted alphabetically.
When you add a package with yarn add
or etc. dependencies are sorted, and this can cause confusing diffs if the dependencies were not previously sorted.
This is fixed by sorting deps by key alphabetically.
repository
fieldKey: INCORRECT_REPOSITORY_FIELD
If a GitHub repo URL is in the repository
field in the root package.json
, all of the packages should have a repository
field which goes into the directory of the package.
Having a repository
field is helpful so there is a link to the source of a package on npm but setting that field on every package and making sure it's correct is error prone and time consuming.
This is fixed by setting the correct URL.
workspace:
protocol requiredKey: WORKSPACE_REQUIRED
If "workspaceProtocol": "require"
is set in the manypkg
config in the root package.json
, all dependencies on internal packages are required to use the workspace:
protocol.
If you want to enforce the usage of the workspace:
protocol.
Dependencies are changed to workspace:^
. Anything else is also allowed after the workspace:
though.
Copyright (c) 2023 Thinkmill Labs Pty Ltd. Licensed under the MIT License.