Operations 15 min read

Implementing a Monorepo with Lerna, Yarn Workspaces, and TypeScript for Automated NPM Package Publishing

By migrating the “全民 K 歌” base library to a monorepo using Lerna, Yarn workspaces, and TypeScript, the team unified dependency management, enabled topological builds, enforced master‑only versioning with conventional commits, and automated CI‑driven publishing—including beta canary releases—significantly speeding iteration and improving maintenance.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Implementing a Monorepo with Lerna, Yarn Workspaces, and TypeScript for Automated NPM Package Publishing

Lerna has become the preferred tool for building monorepo projects, but the official documentation lacks guidance for the final steps of monorepo construction. This article records the experience of migrating the “全民 K 歌” base library to a monorepo using Lerna, Yarn workspaces, TypeScript, and Orange CI.

Background

The original base library was mixed with business code, causing issues such as hidden code, lack of package classification, poor iteration speed, difficulty tracing changes, no version management, and missing documentation.

Code Management Options Comparison

1. Git Submodule / Subtree – easy to feed changes but high management cost with many submodules.

2. Multirepo – clear module division, each module has its own repo, but version control and dependency handling become manual.

3. Monorepo – unified code management, easier issue handling and changelog generation, but requires unified CI/build pipelines and larger repository size.

Monorepo was chosen as the optimal solution.

Implementation

1. Dependency Management

Using Yarn workspaces instead of npm link reduces conflicts and simplifies dependency resolution. Important commands:

lerna bootstrap

Yarn uses a single yarn.lock instead of multiple package-lock.json files.

Run yarn why <query> to understand why a package is installed.

Yarn workspaces are the underlying mechanism used by Lerna.

Root package.json must contain:

{
  "name": "root",
  "private": true,
  "workspaces": ["packages/*"]
}

2. Project Initialization

Initialize Lerna with independent versioning:

lerna init --independent

Create packages:

lerna create @tencent/pkg1
lerna create @tencent/pkg2

Each package’s package.json includes name, version, main, and types fields.

Install TypeScript at the root:

yarn add typescript -W -D

Root tsconfig.json defines compiler options, path mapping, and includes all packages.

Package‑specific tsconfig.json extends the root config and sets outDir and include .

Resulting directory structure:

├── lerna.json
├── yarn.lock
├── package.json
├── packages
│   ├── pkg1
│   │   ├── package.json
│   │   ├── src/index.ts
│   │   └── tsconfig.json
│   └── pkg2
│       ├── package.json
│       ├── src/index.ts
│       └── tsconfig.json
└── tsconfig.json

3. Build Process

Monorepo builds must respect inter‑package dependencies. Two approaches:

Use lerna run to execute scripts in topological order.

Leverage TypeScript Project References (TS 3.0+).

Typical lerna run workflow runs each package’s build script after its dependencies are built.

Project References require each package’s tsconfig.json to set composite: true and list dependent packages in references .

4. Versioning and Publishing

Key requirements:

Code review before publishing.

Publish only from the master branch.

CI should automatically build, bump versions, and generate changelogs.

Support temporary “beta” releases for testing.

Enforce branch restriction in lerna.json :

{
  "packages": ["packages/*"],
  "version": "independent",
  "command": {
    "version": {
      "allowBranch": "master"
    }
  },
  "useWorkspaces": true,
  "npmClient": "yarn"
}

CI scripts in package.json :

{
  "scripts": {
    "clean": "rm -rf ./lib && rm -rf tsconfig.build.tsbuildinfo",
    "build": "yarn clean && tsc --build",
    "prepublishOnly": "npm run build"
  }
}

Version bump follows Conventional Commits:

feat → minor bump.

fix → patch bump.

BREAKING CHANGE → major bump.

CI runs:

npx lerna version --conventional-graduate --yes
npx lerna publish from-package --legacy-auth $TNPM_USERPASSWORD_BASE64 --yes

Temporary canary releases use:

lerna publish -y --canary --preid "beta.$(git rev-parse --short HEAD)" --pre-dist-tag=beta --legacy-auth xxx

Result

The automated pipeline provides real‑time build notifications via WeChat, generates changelogs automatically, and has been adopted across multiple internal projects, improving iteration speed and maintenance order.

TypeScriptCI/CDMonorepolernanpm publishingYarn Workspaces
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.