Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/microsoft-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,19 @@ jobs:
run: |
echo "Target branch: ${{ github.base_ref }}"
yarn nx release --dry-run --verbose
yarn-constraints:
name: "Check Yarn Constraints"
permissions: {}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: yarn
- name: Check constraints
run: yarn constraints
10 changes: 10 additions & 0 deletions docs/Releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,13 @@ We do this so that our first release will have a proper patch version of 0, as s
```

</details>

## Hermes Compatibility (0.74 and above)

*Note: This is only applicable if Hermes is enabled (I.E: `USE_HERMES=1 pod install`.*

React Native macOS updates the patch version automatically for every change that comes through, so our patch number can differ wildly from that of vanilla React Native.

To ensure the best possible Hermes compatibility, we specify a peer dependency in React Native macOS's `package.json` indicating which version of upstream React Native best corresponds with our own version. For example, our 0.74.34 corresponds to upstream's 0.74.7, and we reflect this connection in [this particular version of `package.json`](https://github.com/microsoft/react-native-macos/blob/2db3abeb5d4318fee3abdff4a4d1a68967223135/packages/react-native/package.json#L103).

For stable branches, the existence of this peer dependency is enforced as part of our CIs via [Yarn Constraints](https://yarnpkg.com/features/constraints).
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@tsconfig/node18": "1.0.1",
"@types/react": "^18.2.6",
"@typescript-eslint/parser": "^7.1.1",
"@yarnpkg/types": "^4.0.1",
"ansi-styles": "^4.2.1",
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
"babel-plugin-syntax-hermes-parser": "0.23.1",
Expand Down Expand Up @@ -94,7 +95,7 @@
"mkdirp": "^0.5.1",
"node-fetch": "^2.2.0",
"nullthrows": "^1.1.1",
"nx": "21.2.4",
"nx": "^21.2.4",
"prettier": "2.8.8",
"prettier-plugin-hermes-parser": "0.23.1",
"react": "18.3.1",
Expand Down
1 change: 1 addition & 0 deletions packages/nx-release-version/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@react-native-macos/nx-release-version",
"version": "0.0.1-dev",
"private": true,
"description": "Nx Release Version Actions for React Native macOS",
"homepage": "https://github.com/microsoft/react-native-macos/tree/HEAD/packages/nx-release-version#readme",
"license": "MIT",
Expand Down
4 changes: 2 additions & 2 deletions packages/rn-tester/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"clean-ios": "rm -rf build/generated/ios Pods Podfile.lock"
},
"dependencies": {
"@react-native/oss-library-example": "0.76.10",
"@react-native/popup-menu-android": "workspace:*",
"@react-native/oss-library-example": "workspace:*",
"@react-native/popup-menu-android": "0.76.9",
"flow-enums-runtime": "^0.0.6",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1"
Expand Down
116 changes: 116 additions & 0 deletions yarn.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// @ts-check

/** @type {import('@yarnpkg/types')} */
const {defineConfig} = require('@yarnpkg/types');

/**
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Context} Context
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Workspace} Workspace
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Dependency} Dependency
*/

/**
* Enforce that react-native-macos declares a peer dependency on react-native on release branches,
* except on the main branch, where there is no published version of React Native to align to.
* @param {Context} context
*/
function expectReactNativePeerDependency({Yarn}) {
const rnmWorkspace = Yarn.workspace({ident: 'react-native-macos'});
if (!rnmWorkspace) {
// Report error on root workspace since react-native-macos doesn't exist
Yarn.workspace().error('react-native-macos workspace must exist in the monorepo');
return;
}

// Check if react-native-macos version is 1000.0.0 - implying we are on the main branch
const isMainBranch = rnmWorkspace.manifest.version === '1000.0.0';
if (!isMainBranch) {
const rnPeerDependency = rnmWorkspace.pkg.peerDependencies.get('react-native');
if (!rnPeerDependency) {
rnmWorkspace.error('react-native-macos must declare a peer dependency on react-native on release branches');
}
}
}

/**
* Enforce that all @react-native/ scoped packages use the same version
* as the react-native peer dependency declared in react-native-macos.
* On the main branch, enforce that we use workspace:* for @react-native/ packages.
* @param {Context} context
*/
function enforceReactNativeVersionConsistency({Yarn}) {
const rnmWorkspace = Yarn.workspace({ident: 'react-native-macos'});
if (!rnmWorkspace) {
// Report error on root workspace since react-native-macos doesn't exist
Yarn.workspace().error('react-native-macos workspace must exist in the monorepo');
return;
}

// Check if react-native-macos version is 1000.0.0 - implying we are on the main branch
const isMainBranch = rnmWorkspace.manifest.version === '1000.0.0';

let targetVersion;
if (isMainBranch) {
// On main branch, use workspace:* for @react-native/ packages
targetVersion = 'workspace:*';
} else {
const rnPeerDependency = rnmWorkspace.pkg.peerDependencies.get('react-native');
if (!rnPeerDependency) {
rnmWorkspace.error('react-native-macos must declare a peer dependency on react-native on release branches');
return;
}
targetVersion = rnPeerDependency;
} // Enforce this version on all @react-native/ scoped packages across all workspaces
for (const dependency of Yarn.dependencies()) {
if (dependency.ident.startsWith('@react-native/')) {
// Check if the target package is private (not published)
const targetWorkspace = Yarn.workspace({ident: dependency.ident});
const isPrivatePackage = targetWorkspace && targetWorkspace.manifest.private;

if (isPrivatePackage) {
// Private packages should always use workspace:* since they're not published
dependency.update('workspace:*');
} else {
dependency.update(targetVersion);
}
}
}
}

/**
* Enforce that all @react-native-macos/ scoped packages use the same version
* as react-native-macos, but only for non-private packages.
* @param {Context} context
*/
function enforceReactNativeMacosVersionConsistency({Yarn}) {
const rnmWorkspace = Yarn.workspace({ident: 'react-native-macos'});
if (!rnmWorkspace) {
// Report error on root workspace since react-native-macos doesn't exist
Yarn.workspace().error('react-native-macos workspace must exist in the monorepo');
return;
}

const targetVersion = rnmWorkspace.manifest.version;
if (!targetVersion) {
rnmWorkspace.error('react-native-macos must have a version');
return;
}

// Enforce this version on all non-private @react-native-macos/ scoped packages
for (const workspace of Yarn.workspaces()) {
const isReactNativeMacosScoped = workspace.ident && workspace.ident.startsWith('@react-native-macos/');
const isPrivate = workspace.manifest.private;

if (isReactNativeMacosScoped && !isPrivate) {
workspace.set('version', targetVersion);
}
}
}

module.exports = defineConfig({
constraints: async ctx => {
expectReactNativePeerDependency(ctx);
enforceReactNativeVersionConsistency(ctx);
enforceReactNativeMacosVersionConsistency(ctx);
},
});
Loading