How to enforce package-lock.json

Hey @pkvpraveen,

I think the question should be:

Does npm install still ignore package-lock.json to which I believe the answer is yes:

npm ci is the command that npm uses to match all versions with package-lock.json and we do not currently support that.

What’s the alternative here? This issue already went through its first birthday!

None that I’m aware of. npm install seems to ignore it, and we don’t support npm ci yet. I believe yarn respects yarn.lock?

I just experienced an issue related to this. I updated a dependency using npm update, which updates the package-lock.json without changing the package.json file. I pushed to main and was surprised to see that my deployed site did not include the change. Clearing cache and rebuilding caused my new dependency version to be used.

That’s very surprising. I understand wanting to cache node_modules directly to save time on the installation, but this is an antipattern that is discouraged for CI/CD systems. At the very least, though, I’d expect a hash of the package-lock.json file to be used as a cache key. If that file changes, you cannot simply restore the contents of node_modules. At the very least, a new npm install should be performed so that npm can decide whether any of the packages on disk need to be updated to match the lockfile.

I guess the workaround for now is to add a manual npm install or even npm ci step as a custom plugin or as part of the build step, but I am frankly a bit shocked that I had this problem.

For anyone else hitting this issue, and wanting to be sure that a full npm installation is performed, here’s a custom plugin that you can add to your pipeline (Create Build Plugins | Netlify Docs) that seems to do the trick:

const exec = require("child_process").exec;

module.exports = {
  onPreBuild: async () => {
    await execPromise("npm install");
  },
};

function execPromise(cmd) {
  return new Promise((resolve, reject) => {
    const execProcess = exec(cmd, (error, stdout, stderr) => {
      if (error) {
        // eslint-disable-next-line no-console
        console.warn(error);
        reject(error);
      }
      resolve(stdout ? stdout : stderr);
    });
    execProcess.stdout.pipe(process.stdout);
    execProcess.stderr.pipe(process.stderr);
  });
}

Hey there, @IanVS :wave:

Thanks so much for coming back and sharing your solution! This will definitely help future Forums members who encounter something similar.