The original
How to automate the process of distributing packages to NPM, especially when dealing with multiple branches, is a problem that most people will encounter, and it is of greater concern to do so in CI systems.
It always sucks when I do it manually, so I’ve always done it in a highly automated mode. Automated publishing using CI or manual publishing from terminals is not a problem for this pattern.
I haven’t manually typed NPM publish in a while, which is a good thing.
Good testing is a top priority
I’m crazy about 100% test coverage. This isn’t perfect protection against every problem, but it does prevent me from doing something stupid, like thinking I know what I’m doing.
My preferred test library is TAP, but you can use any test library that supports code coverage. If you’re using a test library that doesn’t support out-of-the-box code coverage testing, you can also run any Node.js process with coverage tracking using NYC.
Run NPM I tap -d to install the test library and add the following code to the package.json file:
{
"scripts": {
"test": "tap"
},
"tap": {
"check-coverage": true}}Copy the code
NPM version command
The NPM version command will calculate what the next version number will be, modify your package.json file, and even check it into Git with a tagged tag. The advantage of this is that when there are untracked changes in Git’s working directory, it prevents version increment and provides hook scripts that can do other things before or after version increment.
In the scripts section of the package.json file, I added a preversion script to run my tests:
{
"scripts": {
"preversion": "npm test"."test": "tap"
},
"tap": {
"check-coverage": true}}Copy the code
Now, NPM makes sure the code passes the tests before version increments. If the test does not pass (or it does not have 100% coverage), the current operation will fail and the version command will not execute.
Release version Changes
Now that version increment is complete, it’s time to share it. As preversion completes, the postversion command performs operations after the version increment. So, let’s check it to publish the package.
{
"scripts": {
"postversion": "npm publish"."preversion": "npm test"."test": "tap"
},
"tap": {
"check-coverage": true}}Copy the code
Keep Git in sync with NPM
Now that I’m done pushing to NPM, I have to remember to push the changes to Git. (Many times I’ve forgotten to do this and had problems, which is usually a bad sign.)
Thanks to NPM for providing us with a publish event hook, we can use it like this:
{
"scripts": {
"postpublish": "git push origin --all; git push origin --tags"."postversion": "npm publish"."preversion": "npm test"."test": "tap"
},
"tap": {
"check-coverage": true}}Copy the code
Here two commands are executed. The first command pushes all branches to the remote, and the second pushes all labels (including those representing the new version) to the remote.
Branch and Distribute Tags (Dist-Tags)
Occasionally I find myself developing important features for a new release that is not yet ready for release.
I would modify the script in the function branch by adding a –tag parameter to the NPM publish command to put the branch in a distribution tag instead of the latest tag.
{
"scripts": {
"postversion": "npm publish --tag=next"."postpublish": "git push origin --all; git push origin --tags"."preversion": "npm test"."test": "tap"
},
"tap": {
"check-coverage": true}}Copy the code
Now I can have someone run NPM install my-module@next to try the new pre-release version.
On the other hand, I might want to fix bugs or port features for older versions. To do this, I created a Git branch based on the older version and added a Legacy tag to package.json.
{
"scripts": {
"postversion": "npm publish --tag=legacy"."postpublish": "git push origin --all; git push origin --tags"."preversion": "npm test"."test": "tap"
},
"tap": {
"check-coverage": true}}Copy the code
Bonus: Sign your version
Git supports tagging commit with PGP signatures. To enable NPM to take advantage of this, do the following configuration:
npm config set sign-git-commit true
npm config set sign-git-tag true
Copy the code
If you thought setting up PGP and hooking it up with Git was too much trouble, congratulations, you’re not alone! I’m an old computer nerd, but I can’t play with it. In addition, I was always worried that my key was in a text file on my computer. If they were encrypted, I would have to keep typing in the password, which would be too much trouble.
I’m a big fan of Krypton. It stores your PGP and SSH private keys in a secure repository on the mobile device, and then pushes notifications to allow it to perform operations using those keys. Setup is very simple, and very easy to use, and gives you a two-factor validation of your hardware.
Of course, I use the NPM version command to do all the publishing. Fix the bug and run NPM Version Patch. New feature, run NPM Version Minor. Major changes, run NPM Version Major.
If you use Conventional Commits or something like that, you can even automatically detect what type of description it is; that’s left to the author to exercise.
This approach to automation using NPM scripts can be applied to any system that you will publish and commit. For your next project, try not to trust your fingers so much
PS: NPM configuration is very flexible
You’ll notice THAT I used –tag = in the publish command above. There are many other ways you can configure NPM. You can set any configuration value (including tag in NPM publish) :
- Explicit commands, such as
--tag=whatever
- Configure in the environment
NPM_CONFIG_TAG=whatever
- In the project root directory
.npmrc
In a file, astag = whatever
- In the home directory
.npmrc
In the file - in
/usr/local/etc/npmrc
(On some systems it is/usr/etc/npmrc on some systems
).
These configurations are inherited, so Settings higher up in the list have higher priority.
For CI/CD systems, this means that you can sometimes set environment variables to control the behavior of NPM commands without changing code or adding files. If it’s easier to use files for control (for example, adding.npmrc files to Git), that’s fine.