Installation
2022 Update
npm and yarn workspaces made this package redundant. There are no more symlinks; package managers automatically resolve from within the workspace.
Purpose
Simply speaking, monorepo is a folder of many npm packages. When those packages consume each other as a dependency, we want them to resolve from local monorepo code, not from the internet (from npm).
There are many reasons, the main ones are: 1. this allows to develop many programs at once, whole dependency chains; 2. consume local unpublished (yet) packages, as if they were published; 3. test the whole monorepo, guarantee all programs work; 4. additional synergy from TypeScript which instantly recognises API errors during rebasing; 5. reduces node_modules
footprint.
Before npm/yarn workspaces were invented, that would be achieved by lerna bootstrap
command: external dependencies (programs outside monorepo) would be installed into monorepo root node_modules
, and local dependencies would be symlinked, monorepo program’s node_modules
folder would have symlinks to folders of neighbour programs.
Previously, it was tedious to add a dependency from within the same monorepo. The lerna add
would perform seemingly unnecessary actions: go to the internet, start cleanups and so on. But I only wanted a symlink from folder X
to folder Y/node_modules/X
.
At first, I used to use ln -s
and manually create symlinks. Then decided to create a CLI to automate that. This CLI.
These days (2022), npm or lerna workspaces automatically recognise “neighbour” packages and resolve to those instead of fetching the last published version from npm, so this CLI is largely redundant.
Quick Take
lerna add
runs a chain of actions redundantly validating the packages and calling the internet.
We want the — npm i
but for packages within our monorepo!
That’s what this CLI does:
deplink "detergent"
That’s it. For example, symlink is created at “monorepo-root/packages/object-all-values-equal-to/node_modules/detergent” pointing to “monorepo-root/packages/detergent”.
As you expect, package.json
is updated, CLI’s are linked properly to bin/
and devDeps can be set via a flag:
deplink "detergent" -d
deplink "detergent" --dev
Supports CLIs
As you know, if an npm package is a CLI, when it’s installed, it creates a symlink in node_modules/.bin/
. You can check your node_modules/bin/
folder — you’ll probably find eslint
there. That symlink enables you to call “eslint” in package.json scripts.
lerna-link-dep
detects CLIs and symlinks them correctly.
For example, lerna-clean-changelogs-cli
of ours has the following bin
entry in its package.json
:
"bin": {
"lcc": "cli.js",
"lernacleanchangelog": "cli.js"
}
If you git-cloned our monorepo and used lerna-link-dep
to link a package lerna-clean-changelogs-cli
to another package, two symlinks would be created in node_modules/bin
: one for lcc
and another for lernacleanchangelog
, both pointing to the same file, cli.js
.
PS. If you wonder, what happens if a package is both CLI and normal package (has both “main” and “bin” keys in package.json
)? It will still work — both sets of symlinks will be created. For example, if you checked eslint
package.json, it has both a bin
(it’s a CLI) and a main
export.
Against lerna link
According to lerna link documentation, lerna link
“symlinks together all packages that are dependencies of each other”. But we want just to symlink one!
By the way, it’s not just me, people complain about it at Lerna’s issues board here, here and here.
Well, now those days are gone; we have a simple dependency linker at last.
The finest ingredients
Only the finest dependencies ingredients are used in this CLI:
fs-extra
— for promise-based I/Oexeca
— to run shell processes, theln -s
partmeow
— to bootstrap the CLIupdate-notifier
— to remind users if currently installed CLI is outdated