An old customer reported a bug in one of the older versions of the product, and a decision was made to fix the bug and release a patch for it. The product consisted of an API and a React client that communicated with the API. The fix was purely a back-end fix behind the API, which should not have had any effect on the React client, but when the build server started building the pull request in GitHub, it was failing to build the React client. It was throwing errors on resolving the dependencies. Upon further inspection there was couple of issues found, that raised some red flags of some developers misunderstanding on how to build an application with npm.

The red flags

The first red flag was that when I looked at the CI/CD build script I saw that it was executing an npm install to install the npm packages used by the React client. Never use npm install on your build servers, there is a dedicated npm command for CI/CD.

npm ci

Using npm install on build servers may update the dependency tree which will make builds inconsistent and nondeterministic.

But this was not the only red flag, the second red flag was that when I changed the build script to run npm ci it started failing with an error saying it can only install packages with an existing package-lock.json. This meant that the file was missing. Next, I checked the history of the branch and found out that a developer most likely has thought that since package-lock.json is auto generated, it can be removed, and the build server will generate it again without knowing the purpose of this file.

What is the purpose of package-lock.json

The purpose of package-lock.json is to describe the exact dependency tree that was used by npm to install needed packages and to guarantee a single representation of a dependency tree across deployments and continuous integration. A more detailed description of the file's usage can be found in npm documentation. What this meant for the patch I was trying to build was that now it was generating a different dependency tree that had incompatible versions of dependencies registered and failing the build.

Npm install vs. Npm ci

Before explaining how I fixed the issue, even though you may have already guessed the difference between npm install and npm ci, it is worth explaining it. npm install will generate a new package-lock.json if it does not exist or it will update the dependency tree if it does not match the packages specified in the package.json. npm ci will install packages based on package-lock.json file and if the file does not exist or does not match the packages specified in the package.json it will throw an error and fail.
Note it is better to use npm ci if you are not going to add new packages or update the package version to keep the development consistent with other team members.

Solution and conclusion

To fix the issue I went back in the history of the branch and found the latest package-lock.json file committed. After that I checked if changes on the branch after removal of the file has added or updated any of the referenced packages. Luckily, that was not the case and I just had to commit the file into the branch again. Then, I adjust the CI/CD to run npm ci and everything was good and green.