[AI] Add Yarn constraints to enforce consistent dependency versions (#7229)

* [AI] Add yarn constraints to enforce consistent dependency versions

Adds a `yarn.config.cjs` that uses Yarn 4's built-in constraints feature
to detect when the same dependency is declared with different version
ranges across workspaces. Workspace protocol references and
peerDependencies are excluded from the check.

Also adds a `yarn constraints` convenience script and the `@yarnpkg/types`
dev dependency for type-checked constraint authoring.

https://claude.ai/code/session_01B1xRjZXn6b18anZjo8cbqb

* Add release notes for PR #7229

* Add constraints job to GitHub Actions workflow

* Fix constraints

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Matiss Janis Aboltins
2026-03-17 16:00:34 +00:00
committed by GitHub
parent dfd6e468a6
commit c4ee71409e
6 changed files with 88 additions and 6 deletions

View File

@@ -12,6 +12,16 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
constraints:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Check dependency version consistency
run: yarn constraints
lint:
runs-on: ubuntu-latest
steps:

View File

@@ -57,6 +57,7 @@
"lint": "oxfmt --check . && oxlint --type-aware --quiet",
"lint:fix": "oxfmt . && oxlint --fix --type-aware --quiet",
"install:server": "yarn workspaces focus @actual-app/sync-server --production",
"constraints": "yarn constraints",
"typecheck": "tsgo -p tsconfig.root.json --noEmit && lage typecheck",
"jq": "./node_modules/node-jq/bin/jq",
"prepare": "husky"
@@ -66,6 +67,7 @@
"@types/node": "^22.19.10",
"@types/prompts": "^2.4.9",
"@typescript/native-preview": "^7.0.0-dev.20260309.1",
"@yarnpkg/types": "^4.0.1",
"baseline-browser-mapping": "^2.9.19",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",

View File

@@ -25,8 +25,8 @@
"@r74tech/docusaurus-plugin-panzoom": "^2.4.0",
"clsx": "^2.1.1",
"prism-react-renderer": "^2.4.1",
"react": "^19.2.4",
"react-dom": "^19.2.4"
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.9.2",

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Add Yarn constraints to ensure consistent dependency versions across all workspace packages.

54
yarn.config.cjs Normal file
View File

@@ -0,0 +1,54 @@
// @ts-check
/** @type {import('@yarnpkg/types')} */
const { defineConfig } = require('@yarnpkg/types');
/**
* Enforce that all workspaces use the same version of any shared dependency.
* Workspace protocol references (workspace:*) and peerDependencies are
* excluded since they intentionally use different/wider ranges.
*
* @param {import('@yarnpkg/types').Yarn.Constraints.Context} context
*/
function enforceConsistentDependencies({ Yarn }) {
/** @type {Map<string, Map<string, Array<import('@yarnpkg/types').Yarn.Constraints.Workspace>>>} */
const dependencyVersions = new Map();
for (const workspace of Yarn.workspaces()) {
for (const type of ['dependencies', 'devDependencies']) {
for (const dep of Yarn.dependencies({ workspace, type })) {
if (dep.range.startsWith('workspace:')) continue;
if (!dependencyVersions.has(dep.ident)) {
dependencyVersions.set(dep.ident, new Map());
}
const versions = dependencyVersions.get(dep.ident);
if (!versions.has(dep.range)) {
versions.set(dep.range, []);
}
versions.get(dep.range).push(workspace);
}
}
}
for (const [ident, versions] of dependencyVersions) {
if (versions.size <= 1) continue;
const rangeList = [...versions.keys()].join(', ');
for (const [, workspaces] of versions) {
for (const workspace of workspaces) {
workspace.error(
`Package "${ident}" has inconsistent versions across workspaces (${rangeList}). ` +
`All workspaces must use the same version range.`,
);
}
}
}
}
module.exports = defineConfig({
async constraints(ctx) {
enforceConsistentDependencies(ctx);
},
});

View File

@@ -11242,6 +11242,15 @@ __metadata:
languageName: node
linkType: hard
"@yarnpkg/types@npm:^4.0.1":
version: 4.0.1
resolution: "@yarnpkg/types@npm:4.0.1"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10/f391763cd955356e9aad551b29e8de7bbf68a6c8992af7cdc950ccf53f8aff6695ad81aa4c8a8e7c582786a840a4f30617732e2cb49f4109b971a9242c31c9fc
languageName: node
linkType: hard
"abbrev@npm:^3.0.0":
version: 3.0.1
resolution: "abbrev@npm:3.0.1"
@@ -11322,6 +11331,7 @@ __metadata:
"@types/node": "npm:^22.19.10"
"@types/prompts": "npm:^2.4.9"
"@typescript/native-preview": "npm:^7.0.0-dev.20260309.1"
"@yarnpkg/types": "npm:^4.0.1"
baseline-browser-mapping: "npm:^2.9.19"
cross-env: "npm:^10.1.0"
eslint: "npm:^9.39.2"
@@ -15199,8 +15209,8 @@ __metadata:
"@types/react": "npm:^19.2.14"
clsx: "npm:^2.1.1"
prism-react-renderer: "npm:^2.4.1"
react: "npm:^19.2.4"
react-dom: "npm:^19.2.4"
react: "npm:19.2.4"
react-dom: "npm:19.2.4"
languageName: unknown
linkType: soft
@@ -24779,7 +24789,7 @@ __metadata:
languageName: node
linkType: hard
"react-dom@npm:19.2.4, react-dom@npm:^19.2.4":
"react-dom@npm:19.2.4":
version: 19.2.4
resolution: "react-dom@npm:19.2.4"
dependencies:
@@ -25227,7 +25237,7 @@ __metadata:
languageName: node
linkType: hard
"react@npm:19.2.4, react@npm:^19.2.4":
"react@npm:19.2.4":
version: 19.2.4
resolution: "react@npm:19.2.4"
checksum: 10/18179fe217f67eb2d0bc61cd04e7ad3c282ea09a1dface7eacd71816f62609f4bbf566c447c704335284deb8397b00bca084e0cd60e6f437279a7498e2d0bfe0