A tool for managing JavaScript projects with multiple packages.
About
Splitting up large codebases into separate independently versioned packages
is extremely useful for code sharing. However, making changes across many
repositories is messy and difficult to track, and testing across repositories
gets complicated really fast.
To solve these (and many other) problems, some projects will organize their
codebases into multi-package repostories (sometimes called monorepos). Projects like Babel, React, Angular,
Ember, Meteor, Jest, and many others develop all of their packages within a
single repository.
Lerna is a tool that optimizes the workflow around managing multi-package
repositories with git and npm.
What does a Lerna repo look like?
There's actually very little to it. You have a file system that looks like this:
my-repo/
package.json
packages/
package-1/
package.json
package-2/
package.json
What can Lerna do?
The two primary commands in Lerna are lerna bootstrap
and lerna publish
.
bootstrap
will link dependencies in the monorepo together.
publish
will help publish updated packages.
Getting Started
The instructions below are for Lerna 2.x which is currently in beta and actively being worked on.
We recommend using it instead of 1.x for a new lerna project. Check the wiki if you need to see the 1.x README.
Let's start by installing Lerna globally with npm.
$ npm install --global lerna
$ npm install --global lerna@2.0.0-beta.13
Next we'll create a new git repository:
$ git init lerna-repo
$ cd lerna-repo
And now let's turn it into a Lerna repo:
$ lerna init
Your repository should now look like this:
lerna-repo/
packages/
package.json
lerna.json
This will create a lerna.json
configuration file as well as a packages
folder.
Note: Depending on the project you might want to run this in
--independent
mode (each package is separately versioned), also described in more detail below.
How it works
Lerna projects operate on a single version line. The version is kept in the lerna.json
file at the root of your project under the version
key. When you run lerna publish
, if a module has been updated since the last time a release was made, it will be updated to the new version you're releasing. This means that you only publish a new version of a package when you need to.
Commands
Init
$ lerna init
Create a new lerna repo or upgrade an existing repo to the current version of Lerna.
Lerna assumes you have already created a git repo with git init
.
- Add lerna as a
devDependency
in package.json
if it isn't already there. - Create a
lerna.json
config file to store the version
number. - Create a
packages
folder if it's not created already.
Example output on a new git repo:
> lerna init
$ Lerna v2.0.0-beta.9
$ Creating packages folder.
$ Updating package.json.
$ Creating lerna.json.
$ Successfully created Lerna files
Options
--independent
/-i
– Use independent versioning mode.
Bootstrap
$ lerna bootstrap
Bootstrap (setup) the packages in the current Lerna repo.
Installs all their dependencies and links any cross-dependencies.
- Link together all
packages
that depend on each other (This doesn't use npm link). npm install
all outside dependencies of each package.
Currently, what lerna does to link internal dependencies is replace the
node_modules/package1
with a link to the actual file in the repo.
How bootstrap
works
We'll use babel
as an example.
babel-core
has a dependency on babel-generator
and say source-map
(among others).- Thus in
babel-core
's package.json
, it has them as keys in dependencies
.
{
"name": "babel-core",
...
"dependencies": {
...
"babel-generator": "^6.9.0",
...
"source-map": "^0.5.0"
}
}
- Lerna check's if each dependency is also part of the lerna repo.
babel-generator
is while sourcemap
is not.- Thus
sourcemap
is npm install
ed like normal - But
babel-core/node_modules/babel-generator
is replaced with 2 files
- A
package.json
with keys name
and version
- A
index.js
with the contents module.exports = require("relative-path-to-babel-generator-in-the-lerna-repo")
- This links the
babel-generator
in node_modules
with the actual babel-generator
files.
Publishing
$ lerna publish
Create a new release of the packages that have been updated. Prompts for a new version and updates all the packages on git and npm.
- Publish each module in
packages
that has been updated since the last version to npm with the dist-tag lerna-temp
. - Run the equivalent of
lerna updated
to determine which packages need to be published. - If necessary, increment the
version
key in lerna.json
. - Update the
package.json
of all updated packages to their new versions. - Update all dependencies of the updated packages with the new versions.
- Create a new git commit and tag for the new version.
- Publish update packages to npm.
- Once all packages have been published, remove the
lerna-temp
tags and add the tags to latest
.
A temporary dist-tag is initally used to prevent the case where only some of the packages are published due to an error.
--npm-tag [tagname]
$ lerna publish --npm-tag=next
Publish to npm with the given npm dist-tag (Defaults to latest
).
This option could be used to publish a prerelease or beta version.
The latest
tag is the one that is used when a user does npm install package
.
--canary, -c
$ lerna publish --canary
The canary
flag will publish packages after every successful merge using the sha as part of the tag.
It will take the current version
and append the sha as the new version
. ex: 1.0.0-canary.81e3b443
.
More specifically, canary
flag will append the current git sha to the current version
and publish it.
The use case for this is a per commit level release or a nightly release.
--skip-git
$ lerna publish --skip-git
Don't run any git commands.
Only publish to npm; skip commiting, tagging, and pushing git changes (this only affects publish).
--force-publish [packages]
$ lerna publish --force-publish=package-2,package-4
$ lerna publish --force-publish=*
Force publish for the specified packages (comma-separated) or all packages using *
(skips the git diff check for changed packages).
--yes
$ lerna publish --canary --yes
Skip all confirmation prompts. Would be useful in CI to automatically answer the publish confirmation prompt.
Updated
$ lerna updated
Check which packages
have changed since the last release (the last git tag).
Lerna determines the last git tag created and runs something like git diff --name-only v6.8.1
to get all files changed since that tag.
Diff
$ lerna diff [package?]
$ lerna diff
$ lerna diff package-name
Diff all packages or a single package since the last release.
Similar to lerna updated
. This command runs git diff
.
Ls
$ lerna ls
List all of the public packages in the current Lerna repo.
Run
$ lerna run [script] // runs npm run my-script in all packages that have it
$ lerna run test
$ lerna run build
Run an npm script in each package that contains that script.
Misc
Lerna will log to a lerna-debug.log
file (same as npm-debug.log
) when lerna encounters an error running a command.
Lerna also has support for scoped packages.
Running lerna
without arguments will show all commands/options.
lerna.json
{
"lerna": "2.0.0-beta.9",
"version": "1.1.3",
"publishConfig": {
"ignore": [
"ignored-file",
"*.md"
]
}
}
lerna
: the current version of lerna
being used.version
: the current version of the repository.publishConfig.ignore
: an array of globs that won't be included in lerna updated/publish
. Use this to prevent publishing a new version just because of README.md
typo.