Skip to content

Commit 413c6aa

Browse files
authored
chore(0.77): add Yarn constraints to enforce dependency alignment (#2584)
Backport #2582
1 parent 987672e commit 413c6aa

File tree

7 files changed

+227
-165
lines changed

7 files changed

+227
-165
lines changed

.github/workflows/microsoft-pr.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,19 @@ jobs:
6363
run: |
6464
echo "Target branch: ${{ github.base_ref }}"
6565
yarn nx release --dry-run --verbose
66+
yarn-constraints:
67+
name: "Check Yarn Constraints"
68+
permissions: {}
69+
runs-on: ubuntu-latest
70+
steps:
71+
- uses: actions/checkout@v4
72+
with:
73+
filter: blob:none
74+
fetch-depth: 0
75+
- uses: actions/setup-node@v4
76+
with:
77+
node-version: '22'
78+
- name: Install dependencies
79+
run: yarn
80+
- name: Check constraints
81+
run: yarn constraints

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@tsconfig/node18": "1.0.1",
5757
"@types/react": "^18.2.6",
5858
"@typescript-eslint/parser": "^7.1.1",
59+
"@yarnpkg/types": "^4.0.1",
5960
"ansi-styles": "^4.2.1",
6061
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
6162
"babel-plugin-syntax-hermes-parser": "0.25.1",
@@ -94,7 +95,7 @@
9495
"micromatch": "^4.0.4",
9596
"node-fetch": "^2.2.0",
9697
"nullthrows": "^1.1.1",
97-
"nx": "21.2.4",
98+
"nx": "^21.2.4",
9899
"prettier": "2.8.8",
99100
"prettier-plugin-hermes-parser": "0.25.1",
100101
"react": "18.3.1",

packages/nx-release-version/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"name": "@react-native-macos/nx-release-version",
33
"version": "0.0.1-dev",
4+
"private": true,
45
"description": "Nx Release Version Actions for React Native macOS",
56
"homepage": "https://github.com/microsoft/react-native-macos/tree/HEAD/packages/nx-release-version#readme",
67
"license": "MIT",

packages/react-native-test-library/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
],
2727
"devDependencies": {
2828
"@babel/core": "^7.25.2",
29-
"@react-native/babel-preset": "0.77.0",
29+
"@react-native/babel-preset": "0.77.2",
3030
"react-native-macos": "workspace:*"
3131
},
3232
"peerDependencies": {

packages/rn-tester/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
"e2e-test-ios": "./scripts/maestro-test-ios.sh"
2828
},
2929
"dependencies": {
30-
"@react-native/oss-library-example": "0.77.4",
31-
"@react-native/popup-menu-android": "workspace:*",
30+
"@react-native/oss-library-example": "workspace:*",
31+
"@react-native/popup-menu-android": "0.77.2",
3232
"flow-enums-runtime": "^0.0.6",
3333
"invariant": "^2.2.4",
3434
"nullthrows": "^1.1.1"

yarn.config.cjs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// @ts-check
2+
3+
/** @type {import('@yarnpkg/types')} */
4+
const {defineConfig} = require('@yarnpkg/types');
5+
6+
/**
7+
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Context} Context
8+
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Workspace} Workspace
9+
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Dependency} Dependency
10+
*/
11+
12+
/**
13+
* Enforce that react-native-macos declares a peer dependency on react-native on release branches,
14+
* except on the main branch, where there is no published version of React Native to align to.
15+
* @param {Context} context
16+
*/
17+
function expectReactNativePeerDependency({Yarn}) {
18+
const rnmWorkspace = Yarn.workspace({ident: 'react-native-macos'});
19+
if (!rnmWorkspace) {
20+
// Report error on root workspace since react-native-macos doesn't exist
21+
Yarn.workspace().error('react-native-macos workspace must exist in the monorepo');
22+
return;
23+
}
24+
25+
// Check if react-native-macos version is 1000.0.0 - implying we are on the main branch
26+
const isMainBranch = rnmWorkspace.manifest.version === '1000.0.0';
27+
if (!isMainBranch) {
28+
const rnPeerDependency = rnmWorkspace.pkg.peerDependencies.get('react-native');
29+
if (!rnPeerDependency) {
30+
rnmWorkspace.error('react-native-macos must declare a peer dependency on react-native on release branches');
31+
}
32+
}
33+
}
34+
35+
/**
36+
* Enforce that all @react-native/ scoped packages use the same version
37+
* as the react-native peer dependency declared in react-native-macos.
38+
* On the main branch, enforce that we use workspace:* for @react-native/ packages.
39+
* @param {Context} context
40+
*/
41+
function enforceReactNativeVersionConsistency({Yarn}) {
42+
const rnmWorkspace = Yarn.workspace({ident: 'react-native-macos'});
43+
if (!rnmWorkspace) {
44+
// Report error on root workspace since react-native-macos doesn't exist
45+
Yarn.workspace().error('react-native-macos workspace must exist in the monorepo');
46+
return;
47+
}
48+
49+
// Check if react-native-macos version is 1000.0.0 - implying we are on the main branch
50+
const isMainBranch = rnmWorkspace.manifest.version === '1000.0.0';
51+
52+
let targetVersion;
53+
if (isMainBranch) {
54+
// On main branch, use workspace:* for @react-native/ packages
55+
targetVersion = 'workspace:*';
56+
} else {
57+
const rnPeerDependency = rnmWorkspace.pkg.peerDependencies.get('react-native');
58+
if (!rnPeerDependency) {
59+
rnmWorkspace.error('react-native-macos must declare a peer dependency on react-native on release branches');
60+
return;
61+
}
62+
targetVersion = rnPeerDependency;
63+
} // Enforce this version on all @react-native/ scoped packages across all workspaces
64+
for (const dependency of Yarn.dependencies()) {
65+
if (dependency.ident.startsWith('@react-native/')) {
66+
// Check if the target package is private (not published)
67+
const targetWorkspace = Yarn.workspace({ident: dependency.ident});
68+
const isPrivatePackage = targetWorkspace && targetWorkspace.manifest.private;
69+
70+
if (isPrivatePackage) {
71+
// Private packages should always use workspace:* since they're not published
72+
dependency.update('workspace:*');
73+
} else {
74+
dependency.update(targetVersion);
75+
}
76+
}
77+
}
78+
}
79+
80+
/**
81+
* Enforce that all @react-native-macos/ scoped packages use the same version
82+
* as react-native-macos, but only for non-private packages.
83+
* @param {Context} context
84+
*/
85+
function enforceReactNativeMacosVersionConsistency({Yarn}) {
86+
const rnmWorkspace = Yarn.workspace({ident: 'react-native-macos'});
87+
if (!rnmWorkspace) {
88+
// Report error on root workspace since react-native-macos doesn't exist
89+
Yarn.workspace().error('react-native-macos workspace must exist in the monorepo');
90+
return;
91+
}
92+
93+
const targetVersion = rnmWorkspace.manifest.version;
94+
if (!targetVersion) {
95+
rnmWorkspace.error('react-native-macos must have a version');
96+
return;
97+
}
98+
99+
// Enforce this version on all non-private @react-native-macos/ scoped packages
100+
for (const workspace of Yarn.workspaces()) {
101+
const isReactNativeMacosScoped = workspace.ident && workspace.ident.startsWith('@react-native-macos/');
102+
const isPrivate = workspace.manifest.private;
103+
104+
if (isReactNativeMacosScoped && !isPrivate) {
105+
workspace.set('version', targetVersion);
106+
}
107+
}
108+
}
109+
110+
module.exports = defineConfig({
111+
constraints: async ctx => {
112+
expectReactNativePeerDependency(ctx);
113+
enforceReactNativeVersionConsistency(ctx);
114+
enforceReactNativeMacosVersionConsistency(ctx);
115+
},
116+
});

0 commit comments

Comments
 (0)