For tools in the npm ecosystem, the prepack
script is a useful opportunity to modify the package immediately before publication.
Unfortunately, it is not safe to modify package.json itself as part of the prepack script. Doing so will result in subtly broken packages.
This is true for all npm-compatible tools that I know of, including npm and yarn.
Many projects published using yarn do this:
{
/* UNSAFE DO NOT USE */
"prepack": "pinst --disable",
"postpack": "pinst --enable"
}
The pinst --disable
command edits package.json
and renames the postinstall
script to _postinstall
. The intention of this practice is to disable the postinstall
script prior to publication. Unfortunately, this does not work as expected. The published package will be subtly broken, and may not install correctly now or in the future.
The only solution that I know of is to remove any problematic prepack
scripts, and instead run them some other way before the publish step.
If like the above example, you are using yarn
in combination with pinst
, then you should remove pinst from your prepack
and postpack
scripts and you must remember to always publish as follows:
yarn pinst --disable
yarn publish
yarn pinst --enable
Unfortunately this solution is not very ergonomic, but there is currently no better solution that I know of.
If you are publishing packages from CI, then you can add these extra steps to your CI script.
The npm repository stores two sets of metadata for every package in the repository. The first set of metadata is the package.json
file that is stored in the package tarball itself. The second set of metadata is stored in the npm database. It is possible for the two to differ. There is no agreement on which set of metadata is the source of truth.
When you publish a package to the npm repository, the tool you use to publish the package (e.g. npm or yarn) is responsible for creating both sets of metadata and uploading them both to the repository. If everything works as expected, then the metadata intended for the database will be a strict subset of the metadata stored in the package tarball. Unfortunately, modifying package.json
as part of the prepack
script causes the two sets of metadata to differ. No tool that I know of (including npm or yarn) will report an error in this situation, and nor does the npm repository itself.
In the above example where the prepack
script is pinst --disable
, the package tarball will contain a renamed _postinstall
script as intended, but the package metadata in the npm database will incorrectly contain the original postinstall
script.
Because the metadata in the npm database and the metadata in the package tarball is supposed to be consistent, tools are free to use whichever set of metadata they wish interchangably. This can cause problems if the metadata is not, in fact, consistent.
In this specific example, this means that some tools will run the postinstall
script from the npm database, and some will look in the package tarball instead, find that there is no postinstall
script, and not run it. At the time of writing npm 10.4 and later will run the script from the npm database, and yarn and earlier versions of npm will check the package tarball. But this behaviour is subject to change. The package itself is broken.