How to filter NPM commands to a pattern of workspaces (like filter, include, exclude, glob, grep or subdirectory)?

3.7k Views Asked by At

I'd like to organise a large NPM monorepo such that I can run commands on many (but not all) workspaces according to a category or pattern.

This could be by subdirectory, or by a pattern in the workspace names like a prefix or suffix, but I can't see any way to do either in NPM alone.


In Yarn >= 2, this is possible using --include or --exclude flags with glob patterns on yarn workspaces foreach, for example:

yarn workspaces foreach --include "@namespace/plugin-*" --include "@namespace/preset-*" run build

...and that builds all your workspaces with that namespace and either the plugin- or preset- prefix.


In lerna, the --scope flag also took globs allowing patterns, for example:

lerna run --scope "@namespace/plugin-*" --scope "@namespace/preset-*" run build

But in NPM, as far I can see in NPM's workspaces documentation for v7 and for v8, it doesn't seem to have the same feature:

  • It has a flag --workspace which may be used multiple times to create an array but appears to only accept exact workspace names,
  • ...and a flag --workspaces which appears to run in all workspaces,

...but I can't see any way to either run on all workspaces in a directory or all workspaces matching a pattern. I really don't want to have to list every workspace by name in every script, as there's going to be a lot of them.

2

There are 2 best solutions below

0
On BEST ANSWER

It's not documented (at time of asking), but NPM can filter workspace commands to subdirectories of workspaces by giving the --workspace flag a path as its value. For example:

npm run --workspace packages/presets --workspace packages/plugins build

This will find workspaces that are directly in $project_root/packages/presets/ or $project_root/packages/plugins/, but not workspaces in subdirectories of these subdirectories.

Deeper-nested subdirectories can be included using /**, like:

npm run --workspace packages/** build

The above examples would work with workspaces organised into subdirectories, defined in package.json like:

  "workspaces": [
    "packages/plugins/*",
    "packages/presets/*",
    ...more
  ],

...or a catch-all like:

  "workspaces": [
    "packages/**",
    ...more
  ],

While pointing --workspace to subdirectories of workspaces instead of individual workspaces like this doesn't seem to be documented anywhere at time of writing, it does appear to be an intentional, planned, supported feature. I found a note proposing it deep in one of the rfcs:

Note: Globs are not supported as a valid --workspace argument value, the proposed alternative is to use npm --workspace= in which is a folder containing multiple workspaces.

I couldn't find anything explicitly stating that this proposal was implemented, but I tried reorganising my monorepo into subdirectories and using a subdirectory with the --workspace flag, and it worked.

Their reason for choosing paths rather than glob patterns in workspace names (like yarn does and lerna did) appears to be because of the:

many pitfalls of supporting globs in the variety of supported shells and operational systems

0
On

This answer is very complete. Just a couple of tweaks that worked for me:

I needed to use the npm command/script before the --workspace flag, and I did not needed to use the /** at the end.

npm run <script-name> --workspace parent/directory/

Examples:

npm publish --workspace packages/public/
npm run build --workspace packages/helpers/

This is documented in other parts of npm so it took a bit of digging. The information is generally the same in all places, because the --workspace flag is a configuration for multiple npm commands:

Valid values for the workspace config are either:

Workspace names
Path to a workspace directory
Path to a parent workspace directory (will result in selecting all workspaces within that folder)

I found it documented here and here.

Unfortunately there does not seem to be a way to use globs. Only way to group things would be by parent directory.

Disclaimer: I was going to add a comment to this answer but it was becoming too long and details might have been missed. I think that is the right answer.