node.jsnpmmonoreponpm-workspaces

Prepare standalone directory to deploy package in a monorepo using npm workspaces


I have a monorepo that uses npm workspaces.

root/
  app/
    package.json
  server/
    package.json
  store/
    package.json
  utils/
  package.json

The app/package.json file references private non-published dependencies in its package.json like this:

{
  "dependencies": {
    "@my-scope/server": "file:../server",
    "@my-scope/store": "file:../store",
    "@my-scope/utils": "file:../utils"
  }
}

This is working fine when running locally; npm hoists all the node_modules dependencies to the top of the repo and adds symlinks between our dependent packages. However, things break once I try to produce a .zip file archive for deploying to Azure. I of course don't want to deploy my entire monorepo, just my app package. However, with all the node_modules dependencies hoisted up to the parent directory, this won't work.

I've tried running this inside of the app directory:

npm install --workspaces=false

It produces this which is close to what I'm looking for:

app/
  node_modules/
    direct-dependency-a/
    direct-dependency-b/
    @my-scope/
      server/ -> ../../../server
      store/ -> ../../../store
      utils/ -> ../../../utils

...but it does not install any of the dependencies of the @my-scope/* packages.

How can I produce a completely standalone deployment-ready directory from a npm workspaces monorepo?


Solution

  • I found the key. In addition to running an install with --workspaces=false, adding the --install-links option to the npm install command causes the packages to be installed correctly. It copies the files from the monorepo package instead of linking, and it installs its dependencies as well.

    Caveat: as @RafaelLeite points out in the comments, this doesn't use lockfiles since your lockfile is hosted at the root of the monorepo, and that can cause "unsupervised" version bumps when installing, so this is still not a perfect solution.