The essential toolkit for monorepo managed by lerna/npm/yarn/pnpm/turbo/etc
- sync versions of packages in monorepo (with cli command)
- sync(update) dependencies versions of all packages (with cli command)
- can update to latest version
- can update to specified version
- can specify packages with wildcard characters
- format all package.json files (with cli command)
- check if all packages are qualified to publish to npm (with cli command)
- list all packages(requires
lerna
or@changesets/cli
) (with cli command) - get all packages' meta info
- some other useful utilities
lerna-ci
is designed for monorepo, but it can also be used in standard repo.
# with yarn, install as a devDependency
yarn add lerna-ci -D
# with npm, install as a devDependency
npm install lerna-ci -D
you may also install it to global if you use cli commands frequently(not recommended)
Notice: lerna-ci requires node >=14.6
# sync versions of packages in monorepo, to fix versions of packages in monorepo when they are messed up
yarn lerna-ci synclocal
# sync all packages' dependencies versions
# following command will sync all @babel scoped npm packages and typescript to latest version, but react and react-dom will be set to 16.x
yarn lerna-ci syncremote "@babel/*" "[email protected]" "[email protected]" typescript
# format all package.json files
yarn lerna-ci fixpack
lerna-ci
also provide some cli commands, so that you do some task with a single line code.
sync versions of packages in monorepo, using syncLocal under the hood.
# with yarn
yarn lerna-ci synclocal [source] [--check-only]
# version source, determine where to get the packages' versions, could be:
# git, npm, local, or all, default local
# if check-only is true, it will only check if packages' versions are synced, exit 1 if not synced
# or if you prefer npm
npx lerna-ci synclocal [source] [--check-only]
# check for more options and examples
yarn lerna-ci synclocal --help
# demo
yarn lerna-ci synclocal
It's very useful when local packages versions are messed up, this may lead to some unexpected errors, it can happens in some cases:
- publish a beta version inside a package without using lerna(or other monorepo tools)
- partial success when publish packages with lerna(or other monorepo tools), you may use
yarn lerna-ci synclocal all
to fix it
You may need to run yarn
or npm install
to make your changes take effect.
sync all packages' dependencies versions in monorepo, using syncDeps under the hood.
It will update following kinds of dependencies:
- dependencies
- devDependencies
- optionalDependencies
- peerDependencies
# with yarn
yarn lerna-ci syncdeps <packageNames...> [--check-only]
# packageNames could be a list of package names, or a list of package name with version range, such as:
# "@babel/*" "@babel/core@^7.0.0" "parcel@^2.0.0" "rollup-plugin*"
# package name with asterisk(*) must be quoted
# packageNames not found or not matched will be ignored
# if check-only is true, it will check if any package's dependencies need be synced, exit 1 if found
# or if you prefer npm, must use quotes when specify scoped wildcard package name
npx lerna-ci syncdeps <packageNames...> [--check-only]
# check for more options and examples
yarn lerna-ci syncdeps --help
# demo
## sync all babel related packages' versions to `^7.0.0`, all packages that start with `eslint-plugin-` to latest, and react react-dom to `18.2.0`
yarn lerna-ci syncdeps "@babel/*@^7.0.0" "eslint-plugin-*" [email protected] [email protected]
You may need to run yarn
or npm install
to make your changes take effect.
check if all packages are qualified to publish to npm, using getChanged under the hood if lerna
or @changesets/cli
is available, it will check:
- whether local has uncommitted changes when in git repo if
--check-git
is true - whether local has conflicts when in git repo
- whether local is behind of remote when in git repo
- whether local packages' versions are synced with the latest version when
use-max-version
is true - whether next versions(can be configured via
releaseType
andperiod
) of local packages are occupied on npm and git
it will exit 1 if any of the above conditions is satisfied
yarn lerna-ci canpublish [--releaseType=patch] [--period=alpha]
# releaseType: patch, minor, major, prepatch, preminor, premajor, prerelease
# default patch
# period: a string like alpha, beta, rc, etc, default alpha, only available when releaseType is pre*
# check for more options and examples
yarn lerna-ci canpublish --help
# demo
# check if all (changed) packages are qualified to publish a beta version in patch
yarn lerna-ci canpublish --releaseType=patch --period=beta
format all packages' package.json, using fixpack under the hood
# with yarn
yarn lerna-ci fixpack
# or if you prefer npm
npx lerna-ci fixpack
above command will format all package.json files with default configuration, you can configure fixpack
's params via configuration file
list all changed packages, using getChanged under the hood
# with yarn
yarn lerna-ci changed
# or if you prefer npm
npx lerna-ci changed
above command requires package lerna
or @changesets/cli
to be installed, or it will exit 1.
You should configure lerna
or @changesets/cli
following their documentations and manage your packages under their official guidelines, or this command will not work as expected.
get all packages info in monorepo(including the root package), you can filter with custom options
e.g.
import { getAllPackageDigests } from 'lerna-ci'
// get all packages
getAllPackageDigests().then(res => console.log(res))
// [{name: 'my-package', version: '1.0.1', private: false, location: '/Users/xx/work/monorepo/my-package'}]
// find packages with custom filter function
getAllPackageDigests((digest => digest.name.startWith('@inner/a-'))).then(res => console.log(res))
// find public packages which package name contains '@inner/'
getAllPackageDigests({ignorePrivate: true, keyword: '@inner/'}).then(res => console.log(res))
Type Declarations:
getAllPackageDigests(filter?: IPackageFilterOptions) => Promise<IPackageDigest[]>
/** package filter object */
export interface IPackageFilterObject {
/** whether need private package */
ignorePrivate?: boolean
/** search package contains the keyword */
keyword?: string
}
/** package filter function */
export type IPackageFilter = (pkg: IPackageDigest, index: number, arr: IPackageDigest[]) => boolean
export type IPackageFilterOptions = IPackageFilterObject | IPackageFilter
/**
* package digest info
*/
export interface IPackageDigest {
/** package name */
name: string
/** package version */
version: string
/** whether package is private */
private: boolean
/** package folder full path */
location: string
}
sync versions of packages in monorepo(version info can be fetch from npm or git tag), if they depend each other and dependence version will be rematched.
also available as command synclocal
e.g.
import { syncLocal } from 'lerna-ci'
// return all changed package infos
const updatedPkgs = await syncLocal({ versionSource: 'npm' })
// [{name: 'my-package', version: '1.0.1', private: false, location: '/Users/xx/work/monorepo/my-package'}]
Type Declarations:
syncLocal(options?: ISyncPackageOptions) => Promise<IPackageDigest[]>
export interface ISyncPackageOptions {
/**
* version source, default to `local`
* how to get latest locale package versions: npm, git, local or all
* @default 'all'
*/
versionSource?: EVerSource
/**
* npm/git version strategy
* @default 'latest'
*/
versionStrategy?: IVersionPickStrategy
/**
* filter which package should be synced
*/
packageFilter?: IPackageFilterOptions
/**
* version range strategy
* @default 'retain'
*/
versionRangeStrategy?: IUpgradeVersionStrategy
/**
* only check, with package.json files untouched
* validate package whether need to update, don't change package.json file actually
*/
checkOnly?: boolean
/**
* check whether packages' versions are exactly same
*/
exact?: boolean
}
/**
* upgrade version strategy
* retain: retain the original version range
*/
export type IUpgradeVersionStrategy = '>' | '~' | '^' | '>=' | '' | 'retain' | IVerTransform
/**
* custom version transform
*/
export type IVerTransform = (name: string, newVersion: string, oldVersion: string) => string
Tips: you may need to reinstall your workspace dependence if anything changed
sync packages dependencies(e.g. babel, react, typescript, etc) versions at once
also available as command syncdeps
import { syncDeps } from 'lerna-ci'
// update all packages that depend `react` and `react-dom` to their latest version(will fetch from npm)
// return all changed package infos(aka all packages that depend on these packageNames and be updated)
const updatedPkgs = await syncDeps({ packageNames: ['react', 'react-dom'] })
// [{name: 'my-package', version: '1.0.1', private: false, location: '/Users/xx/work/monorepo/my-package'}]
// as above, but will also update dependence typescript to a fixed version 3.1.0 and parcel to ^2.0.0
const updatedPkgs = await syncDeps({ packageNames: ['react', 'react-dom'], versionMap: { typescript: '=3.1.0', parcel: '2.0.0' } })
// [{name: 'my-package', version: '1.0.1', private: false, location: '/Users/xx/work/monorepo/my-package'}]
Type Declarations:
syncDeps(syncOptions: ISyncDepOptions)=> Promise<IPackageDigest[]>
export interface ISyncDepOptions {
/**
* package names that should update
* will fetch its version from npm by default
* package name can use asterisk, e.g. @babel/*
*
* @example
* ['duplex-message', '@typescript-eslint/parser', '@babel/*', '*plugin*', 'react*']
*/
packageNames?: string[]
/**
* version map<pkgName, version>
* prefer use this as version map if provided
* pkgName can be a pattern like @babel/*
* if packageNames also provided, will fetch missing versions
* @example
* {'@babel/*': '7.0.0', 'parcel': '^2.0.0', '@types/react': '~18.0.0'}
*/
versionMap?: IVersionMap
/**
* npm version strategy
* default to 'max-stable'
*/
versionPickStrategy?: IVersionPickStrategy
/**
* version range strategy, use retain by default
*/
versionRangeStrategy?: IVersionRangeStrategy
/** only check, with package.json files untouched */
checkOnly?: boolean
/**
* update version to the exact given version
* set to false only update when existing version range is not satisfied
* @default true
*/
exact?: boolean
}
Tips: you may need to reinstall your workspaces dependence if anything changed
Make all your package.json files are written in same criterion: sorting fields, validating required fields. This feature is powered by fixpack.
also available as command fixpack
e.g.
import { fixpack } from 'lerna-ci'
const updatedPkgs = await fixpack()
// [{name: 'my-package', version: '1.0.1', private: false, location: '/Users/xx/work/monorepo/my-package'}]
Type Declarations:
fixpack (options?: IFixPackOptions) => Promise<IPackageDigest[]>
export interface IFixPackOptions {
/**
* which package's should be fixed
*/
packageFilter?: IPackageFilterOptions
/**
* package fix configuration
* check source <src/fixpack-all/config.ts> for default configuration
* see https://github.com/HenrikJoreteg/fixpack#configuration for details
*/
config?: any
}
List all changed packages since last release, it requires package lerna
or @changesets/cli
to be installed.
also available as command changed
e.g.
import { getChanged } from 'lerna-ci'
const changedPkgs = await getChanged()
// [{name: 'my-package', version: '1.0.1', private: false, location: '/Users/xx/work/monorepo/my-package'}]
Type Declarations:
getChanged() => Promise<IPackageDigest[]>
get current monorepo preferred npm client
e.g.
import { getRepoNpmClient } from 'lerna-ci'
// will return npm for default if not specified
// get `yarn-next` when yarn version >= 2.0 found
getRepoNpmClient().then(client => console.log(client))
// yarn
get version from npm registry, you can get the latest version or the max version
e.g.
import { getVersionFormRegistry } from 'lerna-ci'
getVersionFormRegistry(options: IGetPkgVersionFromRegistryOptions) => Promise<string | undefined>
export interface IGetPkgVersionFromRegistryOptions {
/** package name */
pkgName: string
/** strategy: latest or max */
versionStrategy?: IVersionPickStrategy
/**
* specified version, to check for existence
* return itself if found, otherwise return empty string
*/
version?: string
/**
* preferred npm client, auto detect if omitted
*/
npmClient?: 'yarn' | 'yarn-next' | 'npm' | 'pnpm'
}
tips: if you want to fetch version from a npm mirror or custom registry, you should specify the mirror in the .yarnrc
or .npmrc
file
batch version of getVersionFormRegistry
, but return an object(key is package name, value is version)
Type Declarations:
getVersionsFromRegistry(options: IGetPkgVersionsFromRegistryOptions) => Promise<string | undefined>
export interface IGetPkgVersionsFromRegistryOptions {
/**
* package names
*/
pkgNames: string[]
/**
* version pick strategy
* max: max package version
* max-stable: max stable package version
* latest: latest release package version
* @default max
*/
versionStrategy?: 'max' | 'latest' | 'max-stable'
/**
* preferred npm client, detect automatically if not provided
*/
npmClient?: 'yarn' | 'yarn-next' | 'npm' | 'pnpm'
}
get monorepo package version map from git tag list(only tags in packageName@versionNumber
like [email protected]
will be recognized).
caution: this api will run git fetch origin --prune --tags
to sync tags from server, local un-pushed tags will be removed
e.g.
import { getPackageVersionsFromGit } from 'lerna-ci'
// will return npm for default if not specified
getPackageVersionsFromGit().then(ver => console.log(ver))
// {'duplex-message': '1.1.2', 'simple-electron-ipc': '1.1.2'}
Type Declarations:
getPackageVersionsFromGit(type: 'latest' | 'max' = 'latest') => Promise<Record<string, string>>
check whether lerna is installed in current repo
e.g.
import { isLernaAvailable } from 'lerna-ci'
isLernaAvailable().then(isInstalled => console.log(isInstalled))
// true
get git root path of current repo
e.g.
import { getGitRoot } from 'lerna-ci'
// return false if not in a git repo
const maxVer = await getGitRoot()
// /User/xx/work/monorepo
get current project's root path (which contains a package.json
file)
e.g.
import { getProjectRoot } from 'lerna-ci'
// throw error if not in a valid frontend project
const maxVer = await getProjectRoot()
// /User/xx/work/monorepo
get max version(compare in semver) from a version list
e.g.
import { maxVersion } from 'lerna-ci'
const maxVer = maxVersion('0.1', '0.0.1', '1.0.0-alpha.1', '1.0.0')
// 1.0.0
pick a value from a list with a custom compare method
e.g.
import { pickOne } from 'lerna-ci'
const picked = pickOne([{name: 'Lisa', age: 10}, {name: 'Janie', age: 12}, {name: 'Marry', age: 9}], (a, b) => a.age - b.age)
// {name: 'Janie', age: 12}
Type Declarations:
pickOne<V>(list: V[], compare: ICompare<V>) => V | undefined
// return `a` if result >= 0, or return `b`
type ICompare<V> = ((a: V, b: V) => -1 | 0 | 1
You may also add config for these commands via following ways(powered by cosmiconfig) so that you don't need to specify the arguments:
- add
lerna-ci
field topackage.json
in the root of the project - add
.lerna-circ
file in the root of the project with json or yaml format - add
.lerna-circ.json
,.lerna-circ.yaml
,.lerna-circ.yml
or.lerna-circ.cjs
file in the root of the project - add
lerna-ci.config.js
orlerna-ci.config.cjs
file in the root of the project
all these configurations should return an object with the following properties:
synclocal
: same as the params of syncPackageVersionssyncremote
: same as the params of syncPackageDependenceVersionfixpack
: same as the params of fixpack
If you are updating from 0.0.x
, you should be careful about following changes:
- default configuration for
fixpack
has been changed, you may restore the former behavior by settingfixpack.config
to old configuration in configuration file - if you are using APIs, most useful APIs are renamed for better understanding, but no feature is removed, you may read docs above to upgrade