NexusCS

Lerna

JavaScript
Quick reference for Lerna - a fast, modern build system for managing and publishing JavaScript/TypeScript monorepos, now maintained by Nx.
monorepo
npm
yarn
pnpm
workspaces

Getting started

Introduction

Lerna is a fast, modern build system for managing JavaScript/TypeScript monorepos. Now maintained by Nx (v5+), it focuses on task running and publishing workflows.

Installation

# Install Lerna
npm install --global lerna

# Or use with npx
npx lerna@latest init

# Initialize new monorepo
mkdir my-monorepo && cd my-monorepo
npx lerna@latest init

Quick Start

# Run command in all packages
lerna run build

# Run tests in parallel
lerna run test --parallel

# Publish packages
lerna publish

# Version packages
lerna version

Modern Lerna (v5+)

Lerna now delegates dependency management to package managers (npm/yarn/pnpm workspaces).

Removed in v9:

  • lerna bootstrap (use npm install)
  • lerna add (use package manager)
  • lerna link (workspaces handle this)

Core Commands

Task Execution

# Run script in all packages
lerna run <script>

# Run in parallel (faster)
lerna run test --parallel

# Run with streaming output
lerna run build --stream

# Skip packages without script
lerna run test --skip-nx-cache

Versioning

# Interactive version bump
lerna version

# Specific bump type
lerna version patch
lerna version minor
lerna version major

# Prerelease versions
lerna version prerelease --preid beta

# Custom version
lerna version 1.2.3

# Graduate prerelease to stable
lerna version --conventional-graduate

Publishing

# Publish changed packages
lerna publish

# Publish from specific tag
lerna publish from-git

# Publish specific version
lerna publish from-package

# Publish with dist-tag
lerna publish --dist-tag next

# Skip npm (only git tags)
lerna publish --no-push

List Packages

# List all packages
lerna list

# Show package paths
lerna list --long

# Show as JSON
lerna list --json

# Show dependency graph
lerna list --graph

# List changed packages
lerna changed

Filtering & Scoping

Scope Filters

# Run in specific package
lerna run build --scope=package-1

# Multiple packages
lerna run test --scope=package-1 --scope=package-2

# Pattern matching
lerna run build --scope='@myorg/*'

# Exclude packages
lerna run test --ignore=package-1

Dependency Filters

# Run in packages that depend on changed
lerna run build --include-dependencies

# Since specific commit
lerna run test --since=main

# Since last tag
lerna run build --since

# Include dependents
lerna run build --include-dependents

Location Filters

# Packages in specific directory
lerna run build --scope='packages/core/*'

# Exclude by path
lerna run test --ignore='packages/experimental/*'

Configuration

lerna.json

{
  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
  "version": "independent",
  "npmClient": "pnpm",
  "command": {
    "publish": {
      "conventionalCommits": true,
      "message": "chore(release): publish"
    },
    "version": {
      "allowBranch": ["main", "next"]
    }
  },
  "packages": ["packages/*"]
}

Version Modes

{
  "version": "1.0.0"
}

Fixed mode - all packages share version

{
  "version": "independent"
}

Independent mode - each package has own version

Nx Integration

{
  "useNx": true,
  "useWorkspaces": true,
  "npmClient": "npm"
}

nx.json Configuration

Task Pipeline

{
  "tasksRunnerOptions": {
    "default": {
      "runner": "nx/tasks-runners/default",
      "options": {
        "cacheableOperations": ["build", "test", "lint"]
      }
    }
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["{projectRoot}/dist"]
    }
  }
}

Caching

{
  "tasksRunnerOptions": {
    "default": {
      "options": {
        "cacheableOperations": ["build", "test"],
        "cacheDirectory": ".nx/cache"
      }
    }
  }
}

Parallel Execution

{
  "parallel": 3,
  "maxParallel": 5
}

Package Manager Integration

NPM Workspaces

// package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*"]
}
// lerna.json
{
  "npmClient": "npm",
  "useWorkspaces": true
}

Yarn Workspaces

// package.json
{
  "private": true,
  "workspaces": {
    "packages": ["packages/*"],
    "nohoist": ["**/react-native"]
  }
}
// lerna.json
{
  "npmClient": "yarn",
  "useWorkspaces": true
}

pnpm Workspaces

# pnpm-workspace.yaml
packages:
  - "packages/*"
  - "apps/*"
// lerna.json
{
  "npmClient": "pnpm",
  "useWorkspaces": true
}

Versioning Strategies

Conventional Commits

# Enable conventional commits
lerna version --conventional-commits

# Prerelease with conventional
lerna version --conventional-prerelease

# Graduate to stable
lerna version --conventional-graduate

Version Options

Option Description
--exact No caret/tilde ranges
--force-publish Force all packages
--create-release github Create GitHub release
--allow-branch main Only version from main
--no-git-tag-version Skip git tag
--no-push Don't push to git
--yes Skip confirmation prompts
--amend Amend previous commit
--conventional-graduate Graduate prerelease to stable

Changelog Generation

# Generate changelogs
lerna version --conventional-commits

# Custom changelog preset
lerna version --changelog-preset angular
// lerna.json
{
  "command": {
    "version": {
      "conventionalCommits": true,
      "changelogPreset": "conventionalcommits"
    }
  }
}

Publishing Workflow

Publish Options

Option Description
--dist-tag next Publish with npm dist-tag
--registry Custom npm registry
--no-verify-access Skip access verification
--contents dist Subdirectory to publish
--otp One-time password for 2FA
--legacy-auth Legacy auth token
--pre-dist-tag Dist-tag for prereleases
--temp-tag Temporary tag during publish
--yes Skip confirmation
--no-git-reset Leave changes after failed push
--force-publish Force publish all packages
--ignore-scripts Don't run lifecycle scripts
--ignore-prepublish Don't run deprecated prepublish

Publish Workflow

# 1. Version packages
lerna version --conventional-commits

# 2. Build packages
lerna run build

# 3. Publish to npm
lerna publish from-git

# Combined: version + publish
lerna publish --conventional-commits

Canary Releases

# Publish canary version
lerna publish --canary

# Canary from specific commit
lerna publish --canary --preid beta

# Canary with custom version
lerna publish --canary minor

Advanced Filtering

Complex Filters

# Changed since branch
lerna run test --since origin/main

# Include all dependencies
lerna run build \
  --scope=my-app \
  --include-dependencies

# Exclude private packages
lerna run build --no-private

# Only private packages
lerna run test --no-sort --private

Environment Variables

# Set concurrency
LERNA_CONCURRENCY=2 lerna run build

# Skip Nx cache
NX_SKIP_NX_CACHE=true lerna run test

# Verbose output
LERNA_VERBOSE=true lerna run build

Filtering by Changes

# Since last release
lerna run test --since

# Since specific ref
lerna run build --since=v1.0.0

# Excluding merge commits
lerna changed --since=HEAD^

# Force all packages
lerna run build --force-publish

Caching & Performance

Task Caching

# Skip cache for specific run
lerna run build --skip-nx-cache

# Clear cache
npx nx reset

# View cache status
lerna run test --verbose

Parallel Execution

# Run in parallel (max CPUs)
lerna run build --parallel

# Limit concurrency
lerna run test --concurrency=2

# Stream output
lerna run build --stream

# No sort (faster startup)
lerna run test --no-sort

Nx Cloud Integration

{
  "tasksRunnerOptions": {
    "default": {
      "runner": "@nrwl/nx-cloud",
      "options": {
        "accessToken": "YOUR_TOKEN",
        "cacheableOperations": ["build", "test", "lint"]
      }
    }
  }
}

Common Workflows

Initial Setup

# Initialize monorepo
npx lerna@latest init

# Install dependencies
npm install

# Create first package
mkdir -p packages/core
cd packages/core
npm init -y

# Configure lerna.json
cat > lerna.json << 'EOF'
{
  "version": "independent",
  "npmClient": "npm",
  "useWorkspaces": true,
  "command": {
    "publish": {
      "conventionalCommits": true
    }
  }
}
EOF

# Configure workspaces
# Edit root package.json: "workspaces": ["packages/*"]

Development Workflow

# Install all dependencies
npm install

# Build all packages
lerna run build

# Watch mode for changed packages
lerna run dev --parallel

# Run tests
lerna run test --since origin/main

# Lint code
lerna run lint --stream

Release Workflow

# 1. Ensure clean working tree
git status

# 2. Pull latest changes
git pull origin main

# 3. Run tests
lerna run test

# 4. Version packages
lerna version --conventional-commits

# 5. Build for production
lerna run build

# 6. Publish to npm
lerna publish from-git

# 7. Push tags and commits
git push --follow-tags

CI/CD Pipeline

# .github/workflows/release.yml
name: Release
on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v3
        with:
          node-version: 18
          registry-url: https://registry.npmjs.org

      - run: npm ci
      - run: lerna run build
      - run: lerna run test

      - name: Version and Publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          lerna publish --yes --conventional-commits

Troubleshooting

Common Issues

Bootstrap deprecated

# ❌ Don't use
lerna bootstrap

# ✅ Use package manager
npm install
# or
yarn install
# or
pnpm install

Add package deprecated

# ❌ Don't use
lerna add lodash --scope=package-1

# ✅ Use package manager
cd packages/package-1
npm install lodash

Link deprecated

# ❌ Don't use
lerna link

# ✅ Workspaces handle linking
npm install

Debug Mode

# Verbose logging
lerna run build --loglevel verbose

# Debug Nx integration
lerna run test --verbose

# Check package resolution
lerna list --long --all

# Verify configuration
lerna info

Cache Issues

# Clear Nx cache
npx nx reset

# Force rebuild without cache
lerna run build --skip-nx-cache

# View cache directory
ls -la .nx/cache

Migration Guide

From Lerna v6 to v9

Removed commands:

  • lerna bootstrap → Use npm install
  • lerna add → Use package manager directly
  • lerna link → Automatic with workspaces

Configuration changes:

// Old lerna.json (v6)
{
  "packages": ["packages/*"],
  "npmClient": "npm",
  "useWorkspaces": true,
  "command": {
    "bootstrap": {
      "hoist": true
    }
  }
}

// New lerna.json (v9)
{
  "version": "independent",
  "npmClient": "npm",
  "useWorkspaces": true,
  "useNx": true
}

From Yarn Workspaces

# 1. Install Lerna
npm install --save-dev lerna

# 2. Initialize Lerna
npx lerna init

# 3. Update lerna.json
cat > lerna.json << 'EOF'
{
  "npmClient": "yarn",
  "useWorkspaces": true,
  "version": "independent"
}
EOF

# 4. Keep existing package.json workspaces
# No changes needed to package.json

# 5. Start using Lerna commands
lerna run build
lerna version
lerna publish

Configuration Reference

lerna.json Schema

{
  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
  "version": "independent",
  "npmClient": "npm",
  "useWorkspaces": true,
  "useNx": true,
  "packages": ["packages/*", "apps/*"],
  "command": {
    "publish": {
      "conventionalCommits": true,
      "message": "chore(release): publish %s",
      "registry": "https://registry.npmjs.org",
      "allowBranch": ["main", "next"],
      "ignoreChanges": ["**/*.md", "**/*.test.js"]
    },
    "version": {
      "allowBranch": ["main"],
      "conventionalCommits": true,
      "changelogPreset": "angular",
      "createRelease": "github",
      "message": "chore(release): %s"
    },
    "run": {
      "stream": true
    }
  },
  "ignoreChanges": ["**/__fixtures__/**", "**/__tests__/**", "**/*.md"]
}

Command Options

Command Description
run Run npm script in packages
exec Run shell command in packages
version Bump version of packages
publish Publish packages to npm
changed List packages changed
diff Show diff since last release
list List all packages
init Initialize Lerna monorepo
info Print debugging information
clean Remove node_modules from all
import Import package with history

Global Options

Option Description
--loglevel Set log level
--concurrency Parallel task limit
--reject-cycles Fail on circular deps
--no-sort Don't sort topologically
--max-buffer Max buffer for child process
--no-progress Disable progress bars
--ci CI mode (no interactive)

Gotchas

Package Hoisting

Lerna no longer manages hoisting. Use package manager features:

// package.json
{
  "workspaces": {
    "packages": ["packages/*"],
    "nohoist": ["**/react-native", "**/react-native/**"]
  }
}

Private Packages

Mark packages as private to prevent accidental publishing:

// packages/internal/package.json
{
  "name": "@myorg/internal",
  "version": "1.0.0",
  "private": true
}

Version Conflicts

Fixed mode can cause issues with incompatible dependencies. Use independent mode:

{
  "version": "independent"
}

Circular Dependencies

Lerna detects circular dependencies by default:

# Fail on cycles
lerna run build --reject-cycles

# Allow cycles (not recommended)
lerna run build --no-reject-cycles

Also see