Skip to content

Instantly share code, notes, and snippets.

@ShishKabab
Created March 23, 2022 18:43
Show Gist options
  • Save ShishKabab/51ae7b6aab9292f865de0ee238c26052 to your computer and use it in GitHub Desktop.
Save ShishKabab/51ae7b6aab9292f865de0ee238c26052 to your computer and use it in GitHub Desktop.
Preparing Lerna workspace packages ordered by dependency

When having multiple packages in a Lerna workspace that depend on each other, Lerna does not run prepare scripts in the order of how they depend on each other. This becomes problematic when they're Typescript packages which need to be built first, so that the packages depending on them can find the type declaration files. Bug: lerna/lerna#2453

To solve this, I've created a script that outputs the packages in the workspace ordered by the dependency graph. You can then just loop over those and execute npm/yarn run prepare.

package.json puts all of these things together. Run the bootstrap script on initial install. You can also use it when you update any packages, but it may be faster to run install-dependencies if you just need to install new dependencies for any of the packages. Remeber that running yarn add or npm install in any of the workspace packages destroys the symlinks between the packages, so you need to edit package.json dependencies by hand, then run yarn install-dependencies in the top-level dir.

import * as _ from 'lodash'
import * as fs from 'fs'
import * as path from 'path'
import * as glob from 'glob'
const toposort = require('toposort')
export async function main() {
const workspaceDir = __dirname
const lernaConfig = JSON.parse(
fs.readFileSync(path.join(workspaceDir, 'lerna.json')).toString(),
)
const packageGlobs: string[] = lernaConfig['packages']
const potentialPackageDirs = _.flatten(
packageGlobs.map((packageGlob) =>
glob.sync(path.join(workspaceDir, packageGlob)),
),
)
const packageDirs = potentialPackageDirs.filter((dir) =>
fs.existsSync(path.join(dir, 'package.json')),
)
const packageInfoList = packageDirs.map((dir) =>
JSON.parse(fs.readFileSync(path.join(dir, 'package.json')).toString()),
)
const packageInfoByName = _.fromPairs(
packageInfoList.map((info) => [info.name, info]),
)
const packageNames = Object.keys(packageInfoByName)
const packageEdges: Array<[string, string]> = []
for (const packageInfo of packageInfoList) {
for (const dependencyList of [
packageInfo.dependencies,
packageInfo.devDependencies ?? [],
]) {
for (const dependency of Object.keys(dependencyList)) {
if (packageInfoByName[dependency]) {
packageEdges.push([packageInfo.name, dependency])
}
}
}
}
const sorted = toposort.array(packageNames, packageEdges).reverse()
console.log(sorted.join(' '))
}
if (require.main === module) {
main()
}
{
"name": "website",
"private": true,
"scripts": {
"bootstrap": "yarn && yarn install-dependencies && yarn prepare-packages",
"prepare-packages": "for scope in `ts-node ./ordered-packages.ts`; do lerna exec --scope $scope 'yarn prepare'; done",
"update-packages": "git submodule update --remote",
"install-dependencies": "lerna bootstrap --force-local --ignore-scripts && npm rebuild"
},
"devDependencies": {
"@types/glob": "5.0.35",
"@types/node": "^14.0.14",
"glob": "^7.1.6",
"lerna": "^3.13.2",
"toposort": "^2.0.2",
"ts-node": "^8.10.2",
"typescript": "^3.9.5"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment