javascripttypescriptyarn-workspacesyarnpkg-v2

How can I tell `yarn workspace` to refer to executables at the root of my JavaScript monorepo?


I have a large JavaScript (TypeScript) monorepo with many subpackages:

root/
  package.json
  packages/
    packageA/
      package.json
    packageB/
      package.json
    ...
    packageZ/
      package.json

Libraries that I share across all subpackages are defined in root/package.json, two examples being vitest and typescript:

root/package.json:

...
"private": true,
"workspaces": [
  "packages/*"
],
"devDependencies": {
  "typescript": "^4.0.0",
  "vitest": "^1.0.0"
},
"dependencies": {
  "lodash": "^4.0.0"
}

So far, so good. The code in the subpackages (workspaces) has access to the libraries defined at the root; e.g., I can call lodash functions.

But say I then want to define a script in a subpackage:

packageA/package.json:

"scripts": {
  "test": "vitest $*",
  "build": "tsc --build"
}

If I try to run these scripts from a workspace, I get errors because yarn doesn't know how to find the executable:

$ yarn workspace packageA test
command not found: vitest
$ yarn workspace packageA build
command not found: tsc

Ok, I can define the dependencies directly in packageA:

packageA/package.json:

"scripts": {
  "test": "vitest $*",
  "build": "tsc --build"
},
"devDependencies": {
  "typescript": "^4.0.0",
  "vitest": "^1.0.0"
}

Now yarn is fine with it:

$ yarn workspace packageA test
... stuff happens ...
$ yarn workspace packageA build
... stuff happens ...

... but I don't want to define these dependencies once for every subpackage (dozens of times) if I can help it. I'd rather define them once, at the root, both for simplicity and to ensure I'm always running exactly the same version in every package.

Is there a way to make this work? How can I refer to these executables at the root from a yarn workspace?


Solution

  • Looking at the options for yarn run there is --top-level (or -T) option to use the root binaries:

    Check the root workspace for scripts and/or binaries instead of the current one

    So you can configure your workspace's package.json like this:

    "name": "a",
    "scripts": {
      "test": "run -T vitest $*",
      "build": "run -T tsc --build"
    }
    

    Allowing you to run yarn workspace a test, and yarn workspace a build.

    There is a discussion on GitHub that provides an answer. Also, a similar question and answer from stackoverflow: Yarn 2 root binaries unavailable in all workspaces

    If you want to stick with yarn, another option is adding all scripts in the root package.json file. Something like this:

    "scripts": {
      "test:a": "yarn workspace a run test",
      "build:a": "yarn workspace a run build"
    }
    

    Then you can simply yarn run test:a or yarn test:a, or yarn build:a.