Last active
August 8, 2022 13:12
-
-
Save mukaschultze/3fe4be7fb7a0ae9f525b38a739bdda0d to your computer and use it in GitHub Desktop.
Pin yarn workspace versions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Yarn workspaces currently don't have a way of specifying that a workspace | |
* (i.e., package) should use the worktree (i.e., root package.json) version for | |
* a dependency. Some projects use the @* version descriptor (equivalent to | |
* latest version) to achieve this, although this approach works most of the | |
* time it can easily get the workspace and worktree versions out of sync when | |
* some of the packages are explicitly updated via package.json or when | |
* yarn.lock gets regenerated. | |
* | |
* The alternative to using the @* syntax is using the `file:` version | |
* descriptor, so the workspace uses the package from a given directory, in this | |
* case, the worktree node_modules. The problem with this is that yarn won't be | |
* able to hoist duplicate packages to the worktree node_modules, which is one | |
* of the main benefits of using yarn workspaces. | |
* | |
* To solve this, I've implemented this script that will update the yarn.lock to | |
* sync the versions that are using the @* syntax to the package.json versions, | |
* this will allow yarn to hoist the packages to the worktree node_modules while | |
* still maintaining sane versioning that doesn't break often. | |
* | |
* https://gist.github.com/mukaschultze/3fe4be7fb7a0ae9f525b38a739bdda0d | |
*/ | |
const lockfile = require('@yarnpkg/lockfile'); | |
const path = require('path'); | |
const fs = require('fs'); | |
const getAppRootPath = () => { | |
let cwd = fs.realpathSync(process.cwd()).replace('\\', '/'); | |
while (!fs.existsSync(path.join(cwd, 'yarn.lock'))) { | |
const up = path.resolve(cwd, '../').replace('\\', '/'); | |
if (up === cwd) { | |
throw new Error('No yarn.lock found for this project'); | |
} | |
cwd = up; | |
} | |
return cwd; | |
}; | |
const appRoot = getAppRootPath(); | |
const lockfilePath = path.join(appRoot, 'yarn.lock'); | |
const packageJsonPath = path.join(appRoot, 'package.json'); | |
const lockFileText = fs.readFileSync(lockfilePath, 'utf8'); | |
const lockFile = lockfile.parse(lockFileText).object; | |
const packageJsonText = fs.readFileSync(packageJsonPath, 'utf8'); | |
const packageJson = JSON.parse(packageJsonText); | |
// find all packages that have @* version in the lockfile | |
const astheriskPackages = Object.keys(lockFile) | |
.filter((k) => k.endsWith('@*')) | |
.map((k) => k.slice(0, -2)); | |
// from @* versions, filter those that have explicity versions on the | |
// package.json | |
const haveParentVersions = astheriskPackages.filter( | |
(package) => packageJson.dependencies[package] | |
); | |
// make a map from @* to the package json version | |
const parentVersionsMap = new Map( | |
haveParentVersions.map((package) => [ | |
package, | |
packageJson.dependencies[package] | |
]) | |
); | |
const actualPackageVersion = (package) => lockFile[`${package}@*`]; | |
const targetPackageVersion = (package) => | |
lockFile[`${package}@${parentVersionsMap.get(package)}`]; | |
// filter the packages that have @* version in the lockfile that are different | |
// from those in the package.json | |
const needsUpdate = haveParentVersions.filter( | |
(package) => | |
actualPackageVersion(package).version !== | |
targetPackageVersion(package).version | |
); | |
needsUpdate.forEach((package) => { | |
console.warn( | |
`Package ${package} is supposed to be ${ | |
targetPackageVersion(package).version | |
} but is ${actualPackageVersion(package).version}` | |
); | |
lockFile[`${package}@*`] = targetPackageVersion(package); | |
}); | |
const newLockFile = lockfile.stringify(lockFile); | |
fs.writeFileSync(lockfilePath, newLockFile); | |
if (needsUpdate.length > 0) { | |
console.log(`Updated ${needsUpdate.length} packages versions on lockfile`); | |
} else { | |
console.log(`No packages need updating`); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment