diff --git a/.claude/instructions.md b/.claude/instructions.md
new file mode 100644
index 00000000000..39573f6c449
--- /dev/null
+++ b/.claude/instructions.md
@@ -0,0 +1,46 @@
+# React
+
+**Scope**: All code EXCEPT `/compiler/` (compiler has its own instructions).
+
+## Project Structure
+
+| Directory | Purpose |
+|-----------|---------|
+| `/packages/` | Publishable packages (react, react-dom, scheduler, etc.) |
+| `/scripts/` | Build, test, and development scripts |
+| `/fixtures/` | Test applications for manual testing |
+| `/compiler/` | React Compiler (separate sub-project) |
+
+## Key Packages
+
+| Package | Purpose |
+|---------|---------|
+| `react` | Core React library |
+| `react-dom` | DOM renderer |
+| `react-reconciler` | Core reconciliation algorithm |
+| `scheduler` | Cooperative scheduling |
+| `react-server-dom-*` | Server Components |
+| `react-devtools-*` | Developer Tools |
+| `react-refresh` | Fast Refresh runtime |
+
+## Requirements
+
+- **Node**: Must be installed. Stop and prompt user if missing.
+- **Package Manager**: Use `yarn` only.
+
+## Verification
+
+**IMPORTANT**: Use `/verify` to validate all changes before committing.
+
+## Commands
+
+| Command | Purpose |
+|----------|----------------------|
+| `/fix` | Lint and format code |
+| `/test` | Run tests |
+| `/flow` | Type check with Flow |
+| `/flags` | Check feature flags |
+
+## Building
+
+Builds are handled by CI. Do not run locally unless instructed.
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 00000000000..f12f21abf44
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,44 @@
+{
+ "hooks": {
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "if [[ \"$PWD\" != */compiler* ]]; then cat .claude/instructions.md 2>/dev/null || true; fi"
+ }
+ ]
+ }
+ ]
+ },
+ "permissions": {
+ "allow": [
+ "Skill(extract-errors)",
+ "Skill(feature-flags)",
+ "Skill(fix)",
+ "Skill(flags)",
+ "Skill(flow)",
+ "Skill(test)",
+ "Skill(verify)",
+ "Bash(yarn test:*)",
+ "Bash(yarn test-www:*)",
+ "Bash(yarn test-classic:*)",
+ "Bash(yarn test-stable:*)",
+ "Bash(yarn linc:*)",
+ "Bash(yarn lint:*)",
+ "Bash(yarn flow:*)",
+ "Bash(yarn prettier:*)",
+ "Bash(yarn build:*)",
+ "Bash(yarn extract-errors:*)",
+ "Bash(yarn flags:*)"
+ ],
+ "deny": [
+ "Bash(yarn download-build:*)",
+ "Bash(yarn download-build-for-head:*)",
+ "Bash(npm:*)",
+ "Bash(pnpm:*)",
+ "Bash(bun:*)",
+ "Bash(npx:*)"
+ ]
+ }
+}
diff --git a/.claude/skills/extract-errors/SKILL.md b/.claude/skills/extract-errors/SKILL.md
new file mode 100644
index 00000000000..08bb1830eb4
--- /dev/null
+++ b/.claude/skills/extract-errors/SKILL.md
@@ -0,0 +1,12 @@
+---
+name: extract-errors
+description: Use when adding new error messages to React, or seeing "unknown error code" warnings.
+---
+
+# Extract Error Codes
+
+## Instructions
+
+1. Run `yarn extract-errors`
+2. Report if any new errors need codes assigned
+3. Check if error codes are up to date
diff --git a/.claude/skills/feature-flags/SKILL.md b/.claude/skills/feature-flags/SKILL.md
new file mode 100644
index 00000000000..8e3281a1af2
--- /dev/null
+++ b/.claude/skills/feature-flags/SKILL.md
@@ -0,0 +1,79 @@
+---
+name: feature-flags
+description: Use when feature flag tests fail, flags need updating, understanding @gate pragmas, debugging channel-specific test failures, or adding new flags to React.
+---
+
+# React Feature Flags
+
+## Flag Files
+
+| File | Purpose |
+|------|---------|
+| `packages/shared/ReactFeatureFlags.js` | Default flags (canary), `__EXPERIMENTAL__` overrides |
+| `packages/shared/forks/ReactFeatureFlags.www.js` | www channel, `__VARIANT__` overrides |
+| `packages/shared/forks/ReactFeatureFlags.native-fb.js` | React Native, `__VARIANT__` overrides |
+| `packages/shared/forks/ReactFeatureFlags.test-renderer.js` | Test renderer |
+
+## Gating Tests
+
+### `@gate` pragma (test-level)
+
+Use when the feature is completely unavailable without the flag:
+
+```javascript
+// @gate enableViewTransition
+it('supports view transitions', () => {
+ // This test only runs when enableViewTransition is true
+ // and is SKIPPED (not failed) when false
+});
+```
+
+### `gate()` inline (assertion-level)
+
+Use when the feature exists but behavior differs based on flag:
+
+```javascript
+it('renders component', async () => {
+ await act(() => root.render());
+
+ if (gate(flags => flags.enableNewBehavior)) {
+ expect(container.textContent).toBe('new output');
+ } else {
+ expect(container.textContent).toBe('legacy output');
+ }
+});
+```
+
+## Adding a New Flag
+
+1. Add to `ReactFeatureFlags.js` with default value
+2. Add to each fork file (`*.www.js`, `*.native-fb.js`, etc.)
+3. If it should vary in www/RN, set to `__VARIANT__` in the fork file
+4. Gate tests with `@gate flagName` or inline `gate()`
+
+## Checking Flag States
+
+Use `/flags` to view states across channels. See the `flags` skill for full command options.
+
+## `__VARIANT__` Flags (GKs)
+
+Flags set to `__VARIANT__` simulate gatekeepers - tested twice (true and false):
+
+```bash
+/test www # __VARIANT__ = true
+/test www variant false # __VARIANT__ = false
+```
+
+## Debugging Channel-Specific Failures
+
+1. Run `/flags --diff ` to compare values
+2. Check `@gate` conditions - test may be gated to specific channels
+3. Run `/test ` to isolate the failure
+4. Verify flag exists in all fork files if newly added
+
+## Common Mistakes
+
+- **Forgetting both variants** - Always test `www` AND `www variant false` for `__VARIANT__` flags
+- **Using @gate for behavior differences** - Use inline `gate()` if both paths should run
+- **Missing fork files** - New flags must be added to ALL fork files, not just the main one
+- **Wrong gate syntax** - It's `gate(flags => flags.name)`, not `gate('name')`
diff --git a/.claude/skills/fix/SKILL.md b/.claude/skills/fix/SKILL.md
new file mode 100644
index 00000000000..9335409af1d
--- /dev/null
+++ b/.claude/skills/fix/SKILL.md
@@ -0,0 +1,17 @@
+---
+name: fix
+description: Use when you have lint errors, formatting issues, or before committing code to ensure it passes CI.
+---
+
+# Fix Lint and Formatting
+
+## Instructions
+
+1. Run `yarn prettier` to fix formatting
+2. Run `yarn linc` to check for remaining lint issues
+3. Report any remaining manual fixes needed
+
+## Common Mistakes
+
+- **Running prettier on wrong files** - `yarn prettier` only formats changed files
+- **Ignoring linc errors** - These will fail CI, fix them before committing
diff --git a/.claude/skills/flags/SKILL.md b/.claude/skills/flags/SKILL.md
new file mode 100644
index 00000000000..a9ddc331ea9
--- /dev/null
+++ b/.claude/skills/flags/SKILL.md
@@ -0,0 +1,39 @@
+---
+name: flags
+description: Use when you need to check feature flag states, compare channels, or debug why a feature behaves differently across release channels.
+---
+
+# Feature Flags
+
+Arguments:
+- $ARGUMENTS: Optional flags
+
+## Options
+
+| Option | Purpose |
+|--------|---------|
+| (none) | Show all flags across all channels |
+| `--diff ` | Compare flags between channels |
+| `--cleanup` | Show flags grouped by cleanup status |
+| `--csv` | Output in CSV format |
+
+## Channels
+
+- `www`, `www-modern` - Meta internal
+- `canary`, `next`, `experimental` - OSS channels
+- `rn`, `rn-fb`, `rn-next` - React Native
+
+## Legend
+
+โ
enabled, โ disabled, ๐งช `__VARIANT__`, ๐ profiling-only
+
+## Instructions
+
+1. Run `yarn flags $ARGUMENTS`
+2. Explain the output to the user
+3. For --diff, highlight meaningful differences
+
+## Common Mistakes
+
+- **Forgetting `__VARIANT__` flags** - These are tested both ways in www; check both variants
+- **Comparing wrong channels** - Use `--diff` to see exact differences
diff --git a/.claude/skills/flow/SKILL.md b/.claude/skills/flow/SKILL.md
new file mode 100644
index 00000000000..5ef34a20129
--- /dev/null
+++ b/.claude/skills/flow/SKILL.md
@@ -0,0 +1,30 @@
+---
+name: flow
+description: Use when you need to run Flow type checking, or when seeing Flow type errors in React code.
+---
+
+# Flow Type Checking
+
+Arguments:
+- $ARGUMENTS: Renderer to check (default: dom-node)
+
+## Renderers
+
+| Renderer | When to Use |
+|----------|-------------|
+| `dom-node` | Default, recommended for most changes |
+| `dom-browser` | Browser-specific DOM code |
+| `native` | React Native |
+| `fabric` | React Native Fabric |
+
+## Instructions
+
+1. Run `yarn flow $ARGUMENTS` (use `dom-node` if no argument)
+2. Report type errors with file locations
+3. For comprehensive checking (slow), use `yarn flow-ci`
+
+## Common Mistakes
+
+- **Running without a renderer** - Always specify or use default `dom-node`
+- **Ignoring suppressions** - Check if `$FlowFixMe` comments are masking real issues
+- **Missing type imports** - Ensure types are imported from the correct package
diff --git a/.claude/skills/test/SKILL.md b/.claude/skills/test/SKILL.md
new file mode 100644
index 00000000000..e150ad7e549
--- /dev/null
+++ b/.claude/skills/test/SKILL.md
@@ -0,0 +1,46 @@
+---
+name: test
+description: Use when you need to run tests for React core. Supports source, www, stable, and experimental channels.
+---
+
+Run tests for the React codebase.
+
+Arguments:
+- $ARGUMENTS: Channel, flags, and test pattern
+
+Usage Examples:
+- `/test ReactFiberHooks` - Run with source channel (default)
+- `/test experimental ReactFiberHooks` - Run with experimental channel
+- `/test www ReactFiberHooks` - Run with www-modern channel
+- `/test www variant false ReactFiberHooks` - Test __VARIANT__=false
+- `/test stable ReactFiberHooks` - Run with stable channel
+- `/test classic ReactFiberHooks` - Run with www-classic channel
+- `/test watch ReactFiberHooks` - Run in watch mode (TDD)
+
+Release Channels:
+- `(default)` - Source/canary channel, uses ReactFeatureFlags.js defaults
+- `experimental` - Source/experimental channel with __EXPERIMENTAL__ flags = true
+- `www` - www-modern channel with __VARIANT__ flags = true
+- `www variant false` - www channel with __VARIANT__ flags = false
+- `stable` - What ships to npm
+- `classic` - Legacy www-classic (rarely needed)
+
+Instructions:
+1. Parse channel from arguments (default: source)
+2. Map to yarn command:
+ - (default) โ `yarn test --silent --no-watchman `
+ - experimental โ `yarn test -r=experimental --silent --no-watchman `
+ - stable โ `yarn test-stable --silent --no-watchman `
+ - classic โ `yarn test-classic --silent --no-watchman `
+ - www โ `yarn test-www --silent --no-watchman `
+ - www variant false โ `yarn test-www --variant=false --silent --no-watchman `
+3. Report test results and any failures
+
+Hard Rules:
+1. **Use --silent to see failures** - This limits the test output to only failures.
+2. **Use --no-watchman** - This is a common failure in sandboxing.
+
+Common Mistakes:
+- **Running without a pattern** - Runs ALL tests, very slow. Always specify a pattern.
+- **Forgetting both www variants** - Test `www` AND `www variant false` for `__VARIANT__` flags.
+- **Test skipped unexpectedly** - Check for `@gate` pragma; see `feature-flags` skill.
diff --git a/.claude/skills/verify/SKILL.md b/.claude/skills/verify/SKILL.md
new file mode 100644
index 00000000000..daed015e28e
--- /dev/null
+++ b/.claude/skills/verify/SKILL.md
@@ -0,0 +1,24 @@
+---
+name: verify
+description: Use when you want to validate changes before committing, or when you need to check all React contribution requirements.
+---
+
+# Verification
+
+Run all verification steps.
+
+Arguments:
+- $ARGUMENTS: Test pattern for the test step
+
+## Instructions
+
+Run these first in sequence:
+1. Run `yarn prettier` - format code (stop if fails)
+2. Run `yarn linc` - lint changed files (stop if fails)
+
+Then run these with subagents in parallel:
+1. Use `/flow` to type check (stop if fails)
+2. Use `/test` to test changes in source (stop if fails)
+3. Use `/test www` to test changes in www (stop if fails)
+
+If all pass, show success summary. On failure, stop immediately and report the issue with suggested fixes.
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
new file mode 100644
index 00000000000..2164efec0c8
--- /dev/null
+++ b/.codesandbox/ci.json
@@ -0,0 +1,13 @@
+{
+ "packages": ["packages/react", "packages/react-dom", "packages/react-server-dom-webpack", "packages/scheduler"],
+ "buildCommand": "download-build-in-codesandbox-ci",
+ "node": "20",
+ "publishDirectory": {
+ "react": "build/oss-experimental/react",
+ "react-dom": "build/oss-experimental/react-dom",
+ "react-server-dom-webpack": "build/oss-experimental/react-server-dom-webpack",
+ "scheduler": "build/oss-experimental/scheduler"
+ },
+ "sandboxes": ["new"],
+ "silent": true
+}
diff --git a/.editorconfig b/.editorconfig
index 7734a2a8143..48d2b3d27e8 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,4 @@
-# http://editorconfig.org
+# https://editorconfig.org
root = true
[*]
@@ -6,12 +6,11 @@ charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
+insert_final_newline = true
max_line_length = 80
-trim_trailing_whitespace = true
[*.md]
max_line_length = 0
-trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000000..fd9cc6bdca2
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,33 @@
+# Third party
+**/node_modules
+
+# Not written by hand
+packages/react-art/npm/lib
+
+# Build products
+build/
+coverage/
+fixtures/
+scripts/bench/benchmarks/**/*.js
+
+# React repository clone
+scripts/bench/remote-repo/
+
+# Compiler uses its own lint setup
+compiler/
+
+packages/react-devtools-core/dist
+packages/react-devtools-extensions/chrome/build
+packages/react-devtools-extensions/firefox/build
+packages/react-devtools-extensions/shared/build
+packages/react-devtools-extensions/src/ErrorTesterCompiled.js
+packages/react-devtools-fusebox/dist
+packages/react-devtools-inline/dist
+packages/react-devtools-shared/src/hooks/__tests__/__source__/__compiled__/
+packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
+packages/react-devtools-shell/dist
+packages/react-devtools-timeline/dist
+packages/react-devtools-timeline/static
+
+# Imported third-party Flow types
+flow-typed/
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 00000000000..952149f30ca
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,658 @@
+'use strict';
+
+const {
+ es5Paths,
+ esNextPaths,
+} = require('./scripts/shared/pathsByLanguageVersion');
+
+const restrictedGlobals = require('confusing-browser-globals');
+
+const OFF = 0;
+const WARNING = 1;
+const ERROR = 2;
+
+module.exports = {
+ extends: ['prettier', 'plugin:jest/recommended'],
+
+ // Stop ESLint from looking for a configuration file in parent folders
+ root: true,
+
+ reportUnusedDisableDirectives: true,
+
+ plugins: [
+ 'babel',
+ 'ft-flow',
+ 'jest',
+ 'es',
+ 'no-for-of-loops',
+ 'no-function-declare-after-return',
+ 'react',
+ 'react-internal',
+ ],
+
+ parser: 'hermes-eslint',
+ parserOptions: {
+ ecmaVersion: 9,
+ sourceType: 'script',
+ },
+
+ // We're stricter than the default config, mostly. We'll override a few rules
+ // and then enable some React specific ones.
+ rules: {
+ 'ft-flow/array-style-complex-type': [OFF, 'verbose'],
+ 'ft-flow/array-style-simple-type': [OFF, 'verbose'], // TODO should be WARNING
+ 'ft-flow/boolean-style': ERROR,
+ 'ft-flow/no-dupe-keys': ERROR,
+ 'ft-flow/no-primitive-constructor-types': ERROR,
+ 'ft-flow/no-types-missing-file-annotation': OFF, // TODO should be ERROR
+ 'ft-flow/no-unused-expressions': ERROR,
+ // 'ft-flow/no-weak-types': WARNING,
+ // 'ft-flow/require-valid-file-annotation': ERROR,
+ 'es/no-optional-chaining': ERROR,
+ 'no-cond-assign': OFF,
+ 'no-constant-condition': OFF,
+ 'no-control-regex': OFF,
+ 'no-debugger': ERROR,
+ 'no-dupe-args': ERROR,
+ 'no-dupe-keys': ERROR,
+ 'no-duplicate-case': WARNING,
+ 'no-empty-character-class': WARNING,
+ 'no-empty': OFF,
+ 'no-ex-assign': WARNING,
+ 'no-extra-boolean-cast': WARNING,
+ 'no-func-assign': ERROR,
+ 'no-invalid-regexp': WARNING,
+ 'no-irregular-whitespace': WARNING,
+ 'no-negated-in-lhs': ERROR,
+ 'no-obj-calls': ERROR,
+ 'no-regex-spaces': WARNING,
+ 'no-sparse-arrays': ERROR,
+ 'no-unreachable': ERROR,
+ 'use-isnan': ERROR,
+ 'valid-jsdoc': OFF,
+ 'block-scoped-var': OFF,
+ complexity: OFF,
+ 'default-case': OFF,
+ 'guard-for-in': OFF,
+ 'no-alert': OFF,
+ 'no-caller': ERROR,
+ 'no-case-declarations': OFF,
+ 'no-div-regex': OFF,
+ 'no-else-return': OFF,
+ 'no-empty-pattern': WARNING,
+ 'no-eq-null': OFF,
+ 'no-eval': ERROR,
+ 'no-extend-native': WARNING,
+ 'no-extra-bind': WARNING,
+ 'no-fallthrough': WARNING,
+ 'no-implicit-coercion': OFF,
+ 'no-implied-eval': ERROR,
+ 'no-invalid-this': OFF,
+ 'no-iterator': OFF,
+ 'no-labels': [ERROR, {allowLoop: true, allowSwitch: true}],
+ 'no-lone-blocks': WARNING,
+ 'no-loop-func': OFF,
+ 'no-magic-numbers': OFF,
+ 'no-multi-str': ERROR,
+ 'no-native-reassign': [ERROR, {exceptions: ['Map', 'Set']}],
+ 'no-new-func': ERROR,
+ 'no-new': WARNING,
+ 'no-new-wrappers': WARNING,
+ 'no-octal-escape': WARNING,
+ 'no-octal': WARNING,
+ 'no-param-reassign': OFF,
+ 'no-process-env': OFF,
+ 'no-proto': ERROR,
+ 'no-redeclare': OFF, // TODO should be WARNING?
+ 'no-return-assign': OFF,
+ 'no-script-url': ERROR,
+ 'no-self-compare': WARNING,
+ 'no-sequences': WARNING,
+ 'no-throw-literal': ERROR,
+ 'no-useless-call': WARNING,
+ 'no-void': OFF,
+ 'no-warning-comments': OFF,
+ 'no-with': OFF,
+ radix: WARNING,
+ 'vars-on-top': OFF,
+ yoda: OFF,
+ 'init-declarations': OFF,
+ 'no-catch-shadow': ERROR,
+ 'no-delete-var': ERROR,
+ 'no-label-var': WARNING,
+ 'no-shadow-restricted-names': WARNING,
+ 'no-undef-init': OFF,
+ 'no-undef': ERROR,
+ 'no-undefined': OFF,
+ 'callback-return': OFF,
+ 'global-require': OFF,
+ 'handle-callback-err': OFF,
+ 'no-mixed-requires': OFF,
+ 'no-new-require': OFF,
+ 'no-path-concat': OFF,
+ 'no-process-exit': OFF,
+ 'no-restricted-modules': OFF,
+ 'no-sync': OFF,
+ camelcase: [OFF, {properties: 'always'}],
+ 'consistent-this': [OFF, 'self'],
+ 'func-names': OFF,
+ 'func-style': [OFF, 'declaration'],
+ 'id-length': OFF,
+ 'id-match': OFF,
+ 'max-depth': OFF,
+ 'max-nested-callbacks': OFF,
+ 'max-params': OFF,
+ 'max-statements': OFF,
+ 'new-cap': OFF,
+ 'newline-after-var': OFF,
+ 'no-array-constructor': ERROR,
+ 'no-continue': OFF,
+ 'no-inline-comments': OFF,
+ 'no-lonely-if': OFF,
+ 'no-negated-condition': OFF,
+ 'no-nested-ternary': OFF,
+ 'no-new-object': WARNING,
+ 'no-plusplus': OFF,
+ 'no-ternary': OFF,
+ 'no-underscore-dangle': OFF,
+ 'no-unneeded-ternary': WARNING,
+ 'one-var': [WARNING, {initialized: 'never'}],
+ 'operator-assignment': [WARNING, 'always'],
+ 'require-jsdoc': OFF,
+ 'sort-vars': OFF,
+ 'spaced-comment': [
+ OFF,
+ 'always',
+ {exceptions: ['jshint', 'jslint', 'eslint', 'global']},
+ ],
+ 'constructor-super': ERROR,
+ 'no-class-assign': WARNING,
+ 'no-const-assign': ERROR,
+ 'no-dupe-class-members': ERROR,
+ 'no-this-before-super': ERROR,
+ 'object-shorthand': OFF,
+ 'prefer-const': OFF,
+ 'prefer-spread': OFF,
+ 'prefer-reflect': OFF,
+ 'prefer-template': OFF,
+ 'require-yield': OFF,
+ 'babel/generator-star-spacing': OFF,
+ 'babel/new-cap': OFF,
+ 'babel/array-bracket-spacing': OFF,
+ 'babel/object-curly-spacing': OFF,
+ 'babel/object-shorthand': OFF,
+ 'babel/arrow-parens': OFF,
+ 'babel/no-await-in-loop': OFF,
+ 'babel/flow-object-type': OFF,
+ 'react/display-name': OFF,
+ 'react/forbid-prop-types': OFF,
+ 'react/jsx-closing-bracket-location': OFF,
+ 'react/jsx-curly-spacing': OFF,
+ 'react/jsx-equals-spacing': WARNING,
+ 'react/jsx-filename-extension': OFF,
+ 'react/jsx-first-prop-new-line': OFF,
+ 'react/jsx-handler-names': OFF,
+ 'react/jsx-indent': OFF,
+ 'react/jsx-indent-props': OFF,
+ 'react/jsx-key': OFF,
+ 'react/jsx-max-props-per-line': OFF,
+ 'react/jsx-no-bind': OFF,
+ 'react/jsx-no-duplicate-props': ERROR,
+ 'react/jsx-no-literals': OFF,
+ 'react/jsx-no-target-blank': OFF,
+ 'react/jsx-pascal-case': OFF,
+ 'react/jsx-sort-props': OFF,
+ 'react/jsx-uses-vars': ERROR,
+ 'react/no-comment-textnodes': OFF,
+ 'react/no-danger': OFF,
+ 'react/no-deprecated': OFF,
+ 'react/no-did-mount-set-state': OFF,
+ 'react/no-did-update-set-state': OFF,
+ 'react/no-direct-mutation-state': OFF,
+ 'react/no-multi-comp': OFF,
+ 'react/no-render-return-value': OFF,
+ 'react/no-set-state': OFF,
+ 'react/no-string-refs': OFF,
+ 'react/no-unknown-property': OFF,
+ 'react/prefer-es6-class': OFF,
+ 'react/prefer-stateless-function': OFF,
+ 'react/prop-types': OFF,
+ 'react/require-extension': OFF,
+ 'react/require-optimization': OFF,
+ 'react/require-render-return': OFF,
+ 'react/sort-comp': OFF,
+ 'react/sort-prop-types': OFF,
+
+ 'accessor-pairs': OFF,
+ 'brace-style': [ERROR, '1tbs'],
+ 'consistent-return': OFF,
+ 'dot-location': [ERROR, 'property'],
+ // We use console['error']() as a signal to not transform it:
+ 'dot-notation': [ERROR, {allowPattern: '^(error|warn)$'}],
+ 'eol-last': ERROR,
+ eqeqeq: [ERROR, 'allow-null'],
+ indent: OFF,
+ 'jsx-quotes': [ERROR, 'prefer-double'],
+ 'keyword-spacing': [ERROR, {after: true, before: true}],
+ 'no-bitwise': OFF,
+ 'no-console': OFF,
+ 'no-inner-declarations': [ERROR, 'functions'],
+ 'no-multi-spaces': ERROR,
+ 'no-restricted-globals': [ERROR].concat(restrictedGlobals),
+ 'no-restricted-syntax': [
+ ERROR,
+ 'WithStatement',
+ {
+ selector: 'MemberExpression[property.name=/^(?:substring|substr)$/]',
+ message: 'Prefer string.slice() over .substring() and .substr().',
+ },
+ ],
+ 'no-shadow': ERROR,
+ 'no-unused-vars': [ERROR, {args: 'none', ignoreRestSiblings: true}],
+ 'no-use-before-define': OFF,
+ 'no-useless-concat': OFF,
+ quotes: [ERROR, 'single', {avoidEscape: true, allowTemplateLiterals: true}],
+ 'space-before-blocks': ERROR,
+ 'space-before-function-paren': OFF,
+ 'valid-typeof': [ERROR, {requireStringLiterals: true}],
+ // Flow fails with non-string literal keys
+ 'no-useless-computed-key': OFF,
+
+ // We apply these settings to files that should run on Node.
+ // They can't use JSX or ES6 modules, and must be in strict mode.
+ // They can, however, use other ES6 features.
+ // (Note these rules are overridden later for source files.)
+ 'no-var': ERROR,
+ strict: ERROR,
+
+ // Enforced by Prettier
+ // TODO: Prettier doesn't handle long strings or long comments. Not a big
+ // deal. But I turned it off because loading the plugin causes some obscure
+ // syntax error and it didn't seem worth investigating.
+ 'max-len': OFF,
+
+ // React & JSX
+ // Our transforms set this automatically
+ 'react/jsx-boolean-value': [ERROR, 'always'],
+ 'react/jsx-no-undef': ERROR,
+ // We don't care to do this
+ 'react/jsx-sort-prop-types': OFF,
+ 'react/jsx-space-before-closing': ERROR,
+ 'react/jsx-uses-react': ERROR,
+ 'react/no-is-mounted': OFF,
+ // This isn't useful in our test code
+ 'react/react-in-jsx-scope': ERROR,
+ 'react/self-closing-comp': ERROR,
+ // We don't care to do this
+ 'react/jsx-wrap-multilines': [
+ ERROR,
+ {declaration: false, assignment: false},
+ ],
+
+ // Prevent for...of loops because they require a Symbol polyfill.
+ // You can disable this rule for code that isn't shipped (e.g. build scripts and tests).
+ 'no-for-of-loops/no-for-of-loops': ERROR,
+
+ // Prevent function declarations after return statements
+ 'no-function-declare-after-return/no-function-declare-after-return': ERROR,
+
+ // CUSTOM RULES
+ // the second argument of warning/invariant should be a literal string
+ 'react-internal/no-primitive-constructors': ERROR,
+ 'react-internal/safe-string-coercion': [
+ ERROR,
+ {isProductionUserAppCode: true},
+ ],
+ 'react-internal/warning-args': ERROR,
+ 'react-internal/no-production-logging': ERROR,
+ },
+
+ overrides: [
+ {
+ // By default, anything error message that appears the packages directory
+ // must have a corresponding error code. The exceptions are defined
+ // in the next override entry.
+ files: ['packages/**/*.js'],
+ rules: {
+ 'react-internal/prod-error-codes': ERROR,
+ },
+ },
+ {
+ // These are files where it's OK to have unminified error messages. These
+ // are environments where bundle size isn't a concern, like tests
+ // or Node.
+ files: [
+ 'packages/react-dom/src/test-utils/**/*.js',
+ 'packages/react-devtools-shared/**/*.js',
+ 'packages/react-noop-renderer/**/*.js',
+ 'packages/react-refresh/**/*.js',
+ 'packages/react-server-dom-esm/**/*.js',
+ 'packages/react-server-dom-webpack/**/*.js',
+ 'packages/react-server-dom-turbopack/**/*.js',
+ 'packages/react-server-dom-parcel/**/*.js',
+ 'packages/react-server-dom-fb/**/*.js',
+ 'packages/react-server-dom-unbundled/**/*.js',
+ 'packages/react-test-renderer/**/*.js',
+ 'packages/react-debug-tools/**/*.js',
+ 'packages/react-devtools-extensions/**/*.js',
+ 'packages/react-devtools-timeline/**/*.js',
+ 'packages/react-native-renderer/**/*.js',
+ 'packages/eslint-plugin-react-hooks/**/*.js',
+ 'packages/jest-react/**/*.js',
+ 'packages/internal-test-utils/**/*.js',
+ 'packages/**/__tests__/*.js',
+ 'packages/**/npm/*.js',
+ ],
+ rules: {
+ 'react-internal/prod-error-codes': OFF,
+ },
+ },
+ {
+ // We apply these settings to files that we ship through npm.
+ // They must be ES5.
+ files: es5Paths,
+ parser: 'espree',
+ parserOptions: {
+ ecmaVersion: 5,
+ sourceType: 'script',
+ },
+ rules: {
+ 'no-var': OFF,
+ strict: ERROR,
+ },
+ },
+ {
+ // We apply these settings to the source files that get compiled.
+ // They can use all features including JSX (but shouldn't use `var`).
+ files: esNextPaths,
+ parser: 'hermes-eslint',
+ parserOptions: {
+ ecmaVersion: 8,
+ sourceType: 'module',
+ },
+ rules: {
+ 'no-var': ERROR,
+ 'prefer-const': ERROR,
+ strict: OFF,
+ },
+ },
+ {
+ files: ['**/__tests__/*.js'],
+ rules: {
+ // https://github.com/jest-community/eslint-plugin-jest
+ // Meh, who cares.
+ 'jest/consistent-test-it': OFF,
+ // Meh, we have a lot of these, who cares.
+ 'jest/no-alias-methods': OFF,
+ // We do conditions based on feature flags.
+ 'jest/no-conditional-expect': OFF,
+ // We have our own assertion helpers.
+ 'jest/expect-expect': OFF,
+ // Lame rule that fires in itRender helpers or in render methods.
+ 'jest/no-standalone-expect': OFF,
+ },
+ },
+ {
+ // Rules specific to test setup helper files.
+ files: [
+ '**/setupTests.js',
+ '**/setupEnv.js',
+ '**/jest/TestFlags.js',
+ '**/dom-event-testing-library/testHelpers.js',
+ '**/utils/ReactDOMServerIntegrationTestUtils.js',
+ '**/babel/transform-react-version-pragma.js',
+ '**/babel/transform-test-gate-pragma.js',
+ ],
+ rules: {
+ // Some helpers intentionally focus tests.
+ 'jest/no-focused-tests': OFF,
+ // Test fn helpers don't use static text names.
+ 'jest/valid-title': OFF,
+ // We have our own assertion helpers.
+ 'jest/expect-expect': OFF,
+ // Some helpers intentionally disable tests.
+ 'jest/no-disabled-tests': OFF,
+ // Helpers export text function helpers.
+ 'jest/no-export': OFF,
+ // The examples in comments trigger false errors.
+ 'jest/no-commented-out-tests': OFF,
+ },
+ },
+ {
+ files: ['**/jest/TestFlags.js'],
+ rules: {
+ // The examples in comments trigger false errors.
+ 'jest/no-commented-out-tests': OFF,
+ },
+ },
+ {
+ files: [
+ '**/__tests__/**/*.js',
+ 'scripts/**/*.js',
+ 'packages/*/npm/**/*.js',
+ 'packages/dom-event-testing-library/**/*.js',
+ 'packages/react-devtools*/**/*.js',
+ 'dangerfile.js',
+ 'fixtures',
+ 'packages/react-dom/src/test-utils/*.js',
+ ],
+ rules: {
+ 'es/no-optional-chaining': OFF,
+ 'react-internal/no-production-logging': OFF,
+ 'react-internal/warning-args': OFF,
+ 'react-internal/safe-string-coercion': [
+ ERROR,
+ {isProductionUserAppCode: false},
+ ],
+ },
+ },
+ {
+ files: ['scripts/eslint-rules/*.js'],
+ plugins: ['eslint-plugin'],
+ rules: {
+ 'eslint-plugin/prefer-object-rule': ERROR,
+ 'eslint-plugin/require-meta-fixable': [
+ ERROR,
+ {catchNoFixerButFixableProperty: true},
+ ],
+ 'eslint-plugin/require-meta-has-suggestions': ERROR,
+ },
+ },
+ {
+ files: ['packages/react-native-renderer/**/*.js'],
+ globals: {
+ nativeFabricUIManager: 'readonly',
+ RN$enableMicrotasksInReact: 'readonly',
+ },
+ },
+ {
+ files: ['packages/react-server-dom-webpack/**/*.js'],
+ globals: {
+ __webpack_chunk_load__: 'readonly',
+ __webpack_get_script_filename__: 'readonly',
+ __webpack_require__: 'readonly',
+ },
+ },
+ {
+ files: ['packages/react-server-dom-turbopack/**/*.js'],
+ globals: {
+ __turbopack_load_by_url__: 'readonly',
+ __turbopack_require__: 'readonly',
+ },
+ },
+ {
+ files: ['packages/react-server-dom-parcel/**/*.js'],
+ globals: {
+ parcelRequire: 'readonly',
+ },
+ },
+ {
+ files: ['packages/scheduler/**/*.js'],
+ globals: {
+ TaskController: 'readonly',
+ },
+ },
+ {
+ files: [
+ 'packages/react-devtools-extensions/**/*.js',
+ 'packages/react-devtools-shared/src/devtools/views/**/*.js',
+ 'packages/react-devtools-shared/src/hook.js',
+ 'packages/react-devtools-shared/src/backend/console.js',
+ 'packages/react-devtools-shared/src/backend/fiber/renderer.js',
+ 'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
+ 'packages/react-devtools-shared/src/frontend/utils/withPermissionsCheck.js',
+ ],
+ globals: {
+ __IS_CHROME__: 'readonly',
+ __IS_FIREFOX__: 'readonly',
+ __IS_EDGE__: 'readonly',
+ __IS_NATIVE__: 'readonly',
+ __IS_INTERNAL_MCP_BUILD__: 'readonly',
+ __IS_INTERNAL_VERSION__: 'readonly',
+ chrome: 'readonly',
+ },
+ },
+ {
+ files: ['packages/react-devtools-shared/**/*.js'],
+ globals: {
+ __IS_INTERNAL_VERSION__: 'readonly',
+ },
+ },
+ {
+ files: ['packages/react-devtools-*/**/*.js'],
+ excludedFiles: '**/__tests__/**/*.js',
+ plugins: ['eslint-plugin-react-hooks-published'],
+ rules: {
+ 'react-hooks-published/rules-of-hooks': ERROR,
+ },
+ },
+ {
+ files: ['packages/eslint-plugin-react-hooks/src/**/*'],
+ extends: ['plugin:@typescript-eslint/recommended'],
+ parser: '@typescript-eslint/parser',
+ plugins: ['@typescript-eslint', 'eslint-plugin'],
+ rules: {
+ '@typescript-eslint/no-explicit-any': OFF,
+ '@typescript-eslint/no-non-null-assertion': OFF,
+ '@typescript-eslint/array-type': [ERROR, {default: 'generic'}],
+
+ 'es/no-optional-chaining': OFF,
+
+ 'eslint-plugin/prefer-object-rule': ERROR,
+ 'eslint-plugin/require-meta-fixable': [
+ ERROR,
+ {catchNoFixerButFixableProperty: true},
+ ],
+ 'eslint-plugin/require-meta-has-suggestions': ERROR,
+ },
+ },
+ ],
+
+ env: {
+ browser: true,
+ es6: true,
+ node: true,
+ jest: true,
+ },
+
+ globals: {
+ $Flow$ModuleRef: 'readonly',
+ $FlowFixMe: 'readonly',
+ $Keys: 'readonly',
+ $NonMaybeType: 'readonly',
+ $ReadOnly: 'readonly',
+ $ReadOnlyArray: 'readonly',
+ $ArrayBufferView: 'readonly',
+ $Shape: 'readonly',
+ CallSite: 'readonly',
+ ConsoleTask: 'readonly', // TOOD: Figure out what the official name of this will be.
+ ReturnType: 'readonly',
+ AnimationFrameID: 'readonly',
+ WeakRef: 'readonly',
+ // For Flow type annotation. Only `BigInt` is valid at runtime.
+ bigint: 'readonly',
+ BigInt: 'readonly',
+ BigInt64Array: 'readonly',
+ BigUint64Array: 'readonly',
+ CacheType: 'readonly',
+ Class: 'readonly',
+ ClientRect: 'readonly',
+ CopyInspectedElementPath: 'readonly',
+ DOMHighResTimeStamp: 'readonly',
+ EventListener: 'readonly',
+ Iterable: 'readonly',
+ AsyncIterable: 'readonly',
+ $AsyncIterable: 'readonly',
+ $AsyncIterator: 'readonly',
+ Iterator: 'readonly',
+ AsyncIterator: 'readonly',
+ IntervalID: 'readonly',
+ IteratorResult: 'readonly',
+ JSONValue: 'readonly',
+ JSResourceReference: 'readonly',
+ mixin$Animatable: 'readonly',
+ MouseEventHandler: 'readonly',
+ NavigateEvent: 'readonly',
+ Partial: 'readonly',
+ PerformanceMeasureOptions: 'readonly',
+ PropagationPhases: 'readonly',
+ PropertyDescriptor: 'readonly',
+ PropertyDescriptorMap: 'readonly',
+ Proxy$traps: 'readonly',
+ React$Component: 'readonly',
+ React$Config: 'readonly',
+ React$Context: 'readonly',
+ React$Element: 'readonly',
+ React$ElementConfig: 'readonly',
+ React$ElementProps: 'readonly',
+ React$ElementRef: 'readonly',
+ React$ElementType: 'readonly',
+ React$Key: 'readonly',
+ React$Node: 'readonly',
+ React$Portal: 'readonly',
+ React$Ref: 'readonly',
+ React$RefSetter: 'readonly',
+ ReadableStreamController: 'readonly',
+ ReadableStreamReader: 'readonly',
+ RequestInfo: 'readonly',
+ RequestOptions: 'readonly',
+ StoreAsGlobal: 'readonly',
+ symbol: 'readonly',
+ SyntheticEvent: 'readonly',
+ SyntheticMouseEvent: 'readonly',
+ SyntheticPointerEvent: 'readonly',
+ Thenable: 'readonly',
+ TimeoutID: 'readonly',
+ WheelEventHandler: 'readonly',
+ FinalizationRegistry: 'readonly',
+ Exclude: 'readonly',
+ Omit: 'readonly',
+ Keyframe: 'readonly',
+ PropertyIndexedKeyframes: 'readonly',
+ KeyframeAnimationOptions: 'readonly',
+ GetAnimationsOptions: 'readonly',
+ ScrollTimeline: 'readonly',
+ EventListenerOptionsOrUseCapture: 'readonly',
+ FocusOptions: 'readonly',
+ OptionalEffectTiming: 'readonly',
+
+ __REACT_ROOT_PATH_TEST__: 'readonly',
+ spyOnDev: 'readonly',
+ spyOnDevAndProd: 'readonly',
+ spyOnProd: 'readonly',
+ __DEV__: 'readonly',
+ __EXPERIMENTAL__: 'readonly',
+ __EXTENSION__: 'readonly',
+ __PROFILE__: 'readonly',
+ __TEST__: 'readonly',
+ __VARIANT__: 'readonly',
+ __unmockReact: 'readonly',
+ gate: 'readonly',
+ trustedTypes: 'readonly',
+ IS_REACT_ACT_ENVIRONMENT: 'readonly',
+ AsyncLocalStorage: 'readonly',
+ async_hooks: 'readonly',
+ globalThis: 'readonly',
+ navigation: 'readonly',
+ },
+};
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 00000000000..2c19b248459
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,2 @@
+c998bb1ed4b3285398c9c7797135d3f060243c6a
+fd2b3e13d330a4559f5aa21462e1cb2cbbcf144b
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000000..e561f9d2615
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,41 @@
+---
+name: "๐ Bug Report"
+about: Report a reproducible bug or regression.
+title: 'Bug: '
+labels: 'Status: Unconfirmed'
+
+---
+
+
+
+React version:
+
+## Steps To Reproduce
+
+1.
+2.
+
+
+
+Link to code example:
+
+
+
+## The current behavior
+
+
+## The expected behavior
diff --git a/.github/ISSUE_TEMPLATE/compiler_bug_report.yml b/.github/ISSUE_TEMPLATE/compiler_bug_report.yml
new file mode 100644
index 00000000000..a2c0e3d7aa3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/compiler_bug_report.yml
@@ -0,0 +1,64 @@
+name: "โ๏ธ โจ Compiler bug report"
+description: "Report a problem with React Compiler. Please provide enough information that we can reproduce the problem."
+title: "[Compiler Bug]: "
+labels: ["Component: Optimizing Compiler", "Type: Bug", "Status: Unconfirmed"]
+body:
+- type: checkboxes
+ attributes:
+ label: What kind of issue is this?
+ description: |
+ Please indicate if this issue affects the following tools provided by React Compiler.
+ options:
+ - label: React Compiler core (the JS output is incorrect, or your app works incorrectly after optimization)
+ - label: babel-plugin-react-compiler (build issue installing or using the Babel plugin)
+ - label: eslint-plugin-react-hooks (build issue installing or using the eslint plugin)
+ - label: react-compiler-healthcheck (build issue installing or using the healthcheck script)
+- type: input
+ attributes:
+ label: Link to repro
+ description: |
+ Please provide a repro by either sharing a [Playground link](https://playground.react.dev), or a public GitHub repo so the React team can reproduce the error being reported. Please do not share localhost links!
+ placeholder: |
+ e.g. public GitHub repo, or Playground link
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Repro steps
+ description: |
+ What were you doing when the bug happened? Detailed information helps maintainers reproduce and fix bugs.
+
+ Issues filed without repro steps will be closed.
+ placeholder: |
+ Example bug report:
+ 1. Log in with username/password
+ 2. Click "Messages" on the left menu
+ 3. Open any message in the list
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: How often does this bug happen?
+ description: |
+ Following the repro steps above, how easily are you able to reproduce this bug?
+ options:
+ - Every time
+ - Often
+ - Sometimes
+ - Only once
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: What version of React are you using?
+ description: |
+ Please provide your React version in the app where this issue occurred.
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: What version of React Compiler are you using?
+ description: |
+ Please provide the exact React Compiler version in the app where this issue occurred.
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000000..2b1404175e6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: ๐ Documentation Issue
+ url: https://github.com/reactjs/react.dev/issues/new/choose
+ about: This issue tracker is not for documentation issues. Please file documentation issues here.
+ - name: ๐ค Questions and Help
+ url: https://reactjs.org/community/support.html
+ about: This issue tracker is not for support questions. Please refer to the React community's help and discussion forums.
diff --git a/.github/ISSUE_TEMPLATE/devtools_bug_report.yml b/.github/ISSUE_TEMPLATE/devtools_bug_report.yml
new file mode 100644
index 00000000000..f9020976468
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/devtools_bug_report.yml
@@ -0,0 +1,81 @@
+name: "โ๏ธ ๐ DevTools bug report"
+description: "Report a problem with React DevTools. Please provide enough information that we can reproduce the problem."
+title: "[DevTools Bug]: "
+labels: ["Component: Developer Tools", "Type: Bug", "Status: Unconfirmed"]
+body:
+- type: input
+ attributes:
+ label: Website or app
+ description: |
+ Which website or app were you using when the bug happened?
+
+ This should be a public URL, GitHub repo, or Code Sandbox app so the React team can reproduce the error being reported. (Please no localhost URLs.)
+ placeholder: |
+ e.g. website URL, public GitHub repo, or Code Sandbox app
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Repro steps
+ description: |
+ What were you doing on the website or app when the bug happened? Detailed information helps maintainers reproduce and fix bugs.
+
+ Issues filed without repro steps will be closed.
+ placeholder: |
+ Example bug report:
+ 1. Log in with username/password
+ 2. Click "Messages" on the left menu
+ 3. Open any message in the list
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: How often does this bug happen?
+ description: |
+ Following the repro steps above, how easily are you able to reproduce this bug?
+ options:
+ - Every time
+ - Often
+ - Sometimes
+ - Only once
+ validations:
+ required: true
+- type: input
+ id: automated_package
+ attributes:
+ label: DevTools package (automated)
+ description: |
+ Please do not edit this field.
+- type: input
+ id: automated_version
+ attributes:
+ label: DevTools version (automated)
+ description: |
+ Please do not edit this field.
+- type: input
+ id: automated_error_message
+ attributes:
+ label: Error message (automated)
+ description: |
+ Please do not edit this field.
+- type: textarea
+ id: automated_call_stack
+ attributes:
+ label: Error call stack (automated)
+ description: |
+ Please do not edit this field.
+ render: text
+- type: textarea
+ id: automated_component_stack
+ attributes:
+ label: Error component stack (automated)
+ description: |
+ Please do not edit this field.
+ render: text
+- type: textarea
+ id: automated_github_query_string
+ attributes:
+ label: GitHub query string (automated)
+ description: |
+ Please do not edit this field.
+ render: text
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..23fbc65ab57
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,33 @@
+
+
+## Summary
+
+
+
+## How did you test this change?
+
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000000..11800ad7575
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "npm"
+ directories:
+ - "/fixtures/*"
+ schedule:
+ interval: "monthly"
+ open-pull-requests-limit: 0
+ ignore:
+ - dependency-name: "*"
diff --git a/.github/workflows/compiler_discord_notify.yml b/.github/workflows/compiler_discord_notify.yml
new file mode 100644
index 00000000000..5a57cf6a32c
--- /dev/null
+++ b/.github/workflows/compiler_discord_notify.yml
@@ -0,0 +1,49 @@
+name: (Compiler) Discord Notify
+
+on:
+ pull_request_target:
+ types: [opened, ready_for_review]
+ paths:
+ - compiler/**
+ - .github/workflows/compiler_**.yml
+
+permissions: {}
+
+jobs:
+ check_access:
+ if: ${{ github.event.pull_request.draft == false }}
+ runs-on: ubuntu-latest
+ outputs:
+ is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
+ steps:
+ - run: echo ${{ github.event.pull_request.author_association }}
+ - name: Check is member or collaborator
+ id: check_is_member_or_collaborator
+ if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
+ run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
+
+ check_maintainer:
+ if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
+ needs: [check_access]
+ uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
+ permissions:
+ # Used by check_maintainer
+ contents: read
+ with:
+ actor: ${{ github.event.pull_request.user.login }}
+
+ notify:
+ if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
+ needs: check_maintainer
+ runs-on: ubuntu-latest
+ steps:
+ - name: Discord Webhook Action
+ uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
+ with:
+ webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }}
+ embed-author-name: ${{ github.event.pull_request.user.login }}
+ embed-author-url: ${{ github.event.pull_request.user.html_url }}
+ embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
+ embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
+ embed-description: ${{ github.event.pull_request.body }}
+ embed-url: ${{ github.event.pull_request.html_url }}
diff --git a/.github/workflows/compiler_playground.yml b/.github/workflows/compiler_playground.yml
new file mode 100644
index 00000000000..a19e87e25e7
--- /dev/null
+++ b/.github/workflows/compiler_playground.yml
@@ -0,0 +1,69 @@
+name: (Compiler) Playground
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ paths:
+ - compiler/**
+ - .github/workflows/compiler_playground.yml
+
+permissions: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+defaults:
+ run:
+ working-directory: compiler/apps/playground
+
+jobs:
+ playground:
+ name: Test playground
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: compiler/**/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: compiler-and-playground-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ working-directory: compiler
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Check Playwright version
+ id: playwright_version
+ run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
+ - name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
+ id: cache_playwright_browsers
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
+ - run: npx playwright install --with-deps chromium
+ if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
+ - run: CI=true yarn test
+ - run: ls -R test-results
+ if: '!cancelled()'
+ - name: Archive test results
+ if: '!cancelled()'
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results
+ path: compiler/apps/playground/test-results
+ if-no-files-found: ignore
diff --git a/.github/workflows/compiler_prereleases.yml b/.github/workflows/compiler_prereleases.yml
new file mode 100644
index 00000000000..76cd3310b1a
--- /dev/null
+++ b/.github/workflows/compiler_prereleases.yml
@@ -0,0 +1,70 @@
+name: (Compiler) Publish Prereleases
+
+on:
+ workflow_call:
+ inputs:
+ commit_sha:
+ required: true
+ default: ''
+ type: string
+ release_channel:
+ required: true
+ type: string
+ dist_tag:
+ required: true
+ type: string
+ version_name:
+ required: true
+ type: string
+ tag_version:
+ required: false
+ type: string
+ dry_run:
+ required: false
+ type: boolean
+ secrets:
+ NPM_TOKEN:
+ required: true
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+defaults:
+ run:
+ working-directory: compiler
+
+jobs:
+ publish_prerelease:
+ name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - if: inputs.dry_run == true
+ name: Publish packages to npm (dry run)
+ run: |
+ cp ./scripts/release/ci-npmrc ~/.npmrc
+ scripts/release/publish.js --frfr --debug --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}
+ - if: inputs.dry_run != true
+ name: Publish packages to npm
+ run: |
+ cp ./scripts/release/ci-npmrc ~/.npmrc
+ scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}
diff --git a/.github/workflows/compiler_prereleases_manual.yml b/.github/workflows/compiler_prereleases_manual.yml
new file mode 100644
index 00000000000..c4a7a16aca3
--- /dev/null
+++ b/.github/workflows/compiler_prereleases_manual.yml
@@ -0,0 +1,41 @@
+name: (Compiler) Publish Prereleases Manual
+
+on:
+ workflow_dispatch:
+ inputs:
+ prerelease_commit_sha:
+ required: false
+ release_channel:
+ required: true
+ type: string
+ dist_tag:
+ required: true
+ type: string
+ version_name:
+ required: true
+ type: string
+ tag_version:
+ required: false
+ type: string
+ dry_run:
+ required: false
+ type: boolean
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ publish_prerelease_experimental:
+ name: Publish to Experimental channel
+ uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
+ with:
+ commit_sha: ${{ inputs.prerelease_commit_sha || github.sha }}
+ release_channel: ${{ inputs.release_channel }}
+ dist_tag: ${{ inputs.dist_tag }}
+ version_name: ${{ inputs.version_name }}
+ tag_version: ${{ inputs.tag_version }}
+ dry_run: ${{ inputs.dry_run }}
+ secrets:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/compiler_prereleases_nightly.yml b/.github/workflows/compiler_prereleases_nightly.yml
new file mode 100644
index 00000000000..ca2b5589def
--- /dev/null
+++ b/.github/workflows/compiler_prereleases_nightly.yml
@@ -0,0 +1,24 @@
+name: (Compiler) Publish Prereleases Nightly
+
+on:
+ schedule:
+ # At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
+ - cron: 10 16 * * 1,2,3,4,5
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ publish_prerelease_experimental:
+ name: Publish to Experimental channel
+ uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
+ with:
+ commit_sha: ${{ github.sha }}
+ release_channel: experimental
+ dist_tag: experimental
+ version_name: '0.0.0'
+ dry_run: false
+ secrets:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/compiler_typescript.yml b/.github/workflows/compiler_typescript.yml
new file mode 100644
index 00000000000..6a3b52e21ae
--- /dev/null
+++ b/.github/workflows/compiler_typescript.yml
@@ -0,0 +1,108 @@
+name: (Compiler) TypeScript
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ paths:
+ - compiler/**
+ - .github/workflows/compiler_typescript.yml
+
+permissions: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+defaults:
+ run:
+ working-directory: compiler
+
+jobs:
+ discover_yarn_workspaces:
+ name: Discover yarn workspaces
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.set-matrix.outputs.matrix }}
+ steps:
+ - uses: actions/checkout@v4
+ - id: set-matrix
+ run: echo "matrix=$(find packages -mindepth 1 -maxdepth 1 -type d | sed 's!packages/!!g' | tr '\n' ',' | sed s/.$// | jq -Rsc '. / "," - [""]')" >> $GITHUB_OUTPUT
+
+ # Hardcoded to improve parallelism
+ lint:
+ name: Lint babel-plugin-react-compiler
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn workspace babel-plugin-react-compiler lint
+
+ # Hardcoded to improve parallelism
+ jest:
+ name: Jest babel-plugin-react-compiler
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn workspace babel-plugin-react-compiler jest
+
+ test:
+ name: Test ${{ matrix.workspace_name }}
+ needs: discover_yarn_workspaces
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ workspace_name: ${{ fromJSON(needs.discover_yarn_workspaces.outputs.matrix) }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
+ if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'
+ - run: yarn workspace ${{ matrix.workspace_name }} test
+ if: matrix.workspace_name != 'react-forgive'
diff --git a/.github/workflows/devtools_discord_notify.yml b/.github/workflows/devtools_discord_notify.yml
new file mode 100644
index 00000000000..bb498f00371
--- /dev/null
+++ b/.github/workflows/devtools_discord_notify.yml
@@ -0,0 +1,49 @@
+name: (DevTools) Discord Notify
+
+on:
+ pull_request_target:
+ types: [opened, ready_for_review]
+ paths:
+ - packages/react-devtools**
+ - .github/workflows/devtools_**.yml
+
+permissions: {}
+
+jobs:
+ check_access:
+ if: ${{ github.event.pull_request.draft == false }}
+ runs-on: ubuntu-latest
+ outputs:
+ is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
+ steps:
+ - run: echo ${{ github.event.pull_request.author_association }}
+ - name: Check is member or collaborator
+ id: check_is_member_or_collaborator
+ if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
+ run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
+
+ check_maintainer:
+ if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
+ needs: [check_access]
+ uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
+ permissions:
+ # Used by check_maintainer
+ contents: read
+ with:
+ actor: ${{ github.event.pull_request.user.login }}
+
+ notify:
+ if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
+ needs: check_maintainer
+ runs-on: ubuntu-latest
+ steps:
+ - name: Discord Webhook Action
+ uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
+ with:
+ webhook-url: ${{ secrets.DEVTOOLS_DISCORD_WEBHOOK_URL }}
+ embed-author-name: ${{ github.event.pull_request.user.login }}
+ embed-author-url: ${{ github.event.pull_request.user.html_url }}
+ embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
+ embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
+ embed-description: ${{ github.event.pull_request.body }}
+ embed-url: ${{ github.event.pull_request.html_url }}
diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml
new file mode 100644
index 00000000000..9fe0c55e0bd
--- /dev/null
+++ b/.github/workflows/devtools_regression_tests.yml
@@ -0,0 +1,205 @@
+name: (DevTools) Regression Tests
+
+on:
+ schedule:
+ - cron: 0 0 * * *
+ workflow_dispatch:
+ inputs:
+ commit_sha:
+ required: false
+ type: string
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+jobs:
+ download_build:
+ name: Download base build
+ runs-on: ubuntu-latest
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd scripts/release install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Download react-devtools artifacts for base revision
+ run: |
+ git fetch origin main
+ GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || '$(git rev-parse origin/main)' }}
+ - name: Display structure of build
+ run: ls -R build
+ - name: Archive build
+ uses: actions/upload-artifact@v4
+ with:
+ name: build
+ path: build
+ if-no-files-found: error
+
+ build_devtools_and_process_artifacts:
+ name: Build DevTools and process artifacts
+ needs: download_build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ name: build
+ path: build
+ - run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
+ env:
+ RELEASE_CHANNEL: experimental
+ - name: Display structure of build
+ run: ls -R build
+ - name: Archive devtools build
+ uses: actions/upload-artifact@v4
+ with:
+ name: react-devtools
+ path: build/devtools
+ if-no-files-found: error
+ # Simplifies getting the extension for local testing
+ - name: Archive chrome extension
+ uses: actions/upload-artifact@v4
+ with:
+ name: react-devtools-chrome-extension
+ path: build/devtools/chrome-extension.zip
+ if-no-files-found: error
+ - name: Archive firefox extension
+ uses: actions/upload-artifact@v4
+ with:
+ name: react-devtools-firefox-extension
+ path: build/devtools/firefox-extension.zip
+ if-no-files-found: error
+
+ run_devtools_tests_for_versions:
+ name: Run DevTools tests for versions
+ needs: build_devtools_and_process_artifacts
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ version:
+ - "16.0"
+ - "16.5" # schedule package
+ - "16.8" # hooks
+ - "17.0"
+ - "18.0"
+ - "18.2" # compiler polyfill
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore all archived build artifacts
+ uses: actions/download-artifact@v4
+ - name: Display structure of build
+ run: ls -R build
+ - run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }} --replaceBuild
+ - run: node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion ${{ matrix.version }} --ci
+
+ run_devtools_e2e_tests_for_versions:
+ name: Run DevTools e2e tests for versions
+ needs: build_devtools_and_process_artifacts
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ version:
+ - "16.0"
+ - "16.5" # schedule package
+ - "16.8" # hooks
+ - "17.0"
+ - "18.0"
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore all archived build artifacts
+ uses: actions/download-artifact@v4
+ - name: Display structure of build
+ run: ls -R build
+ - name: Check Playwright version
+ id: playwright_version
+ run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
+ - name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
+ id: cache_playwright_browsers
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
+ - run: npx playwright install --with-deps
+ if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
+ - run: npx playwright install-deps
+ if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
+ - run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }}
+ - run: ls -R build-regression
+ - run: ./scripts/ci/run_devtools_e2e_tests.js ${{ matrix.version }}
+ env:
+ RELEASE_CHANNEL: experimental
+ - name: Cleanup build regression folder
+ run: rm -r ./build-regression
+ - uses: actions/upload-artifact@v4
+ with:
+ name: screenshots
+ path: ./tmp/playwright-artifacts
+ if-no-files-found: warn
diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml
new file mode 100644
index 00000000000..3eec5f90bee
--- /dev/null
+++ b/.github/workflows/runtime_build_and_test.yml
@@ -0,0 +1,933 @@
+name: (Runtime) Build and Test
+
+on:
+ push:
+ branches: [main]
+ tags:
+ # To get CI for backport releases.
+ # This will duplicate CI for releases from main which is acceptable
+ - "v*"
+ pull_request:
+ paths-ignore:
+ - compiler/**
+ workflow_dispatch:
+ inputs:
+ commit_sha:
+ required: false
+ type: string
+ default: ''
+
+permissions: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+jobs:
+ # ----- NODE_MODULES CACHE -----
+ # Centralize the node_modules cache so it is saved once and each subsequent job only needs to
+ # restore the cache. Prevents race conditions where multiple workflows try to write to the cache.
+ runtime_node_modules_cache:
+ name: Cache Runtime node_modules
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - name: Check cache hit
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ lookup-only: true
+ - uses: actions/setup-node@v4
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Warm with old cache
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ uses: actions/cache/restore@v4
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Save cache
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ uses: actions/cache/save@v4
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+
+ runtime_compiler_node_modules_cache:
+ name: Cache Runtime, Compiler node_modules
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - name: Check cache hit
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
+ lookup-only: true
+ - uses: actions/setup-node@v4
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: |
+ yarn.lock
+ compiler/yarn.lock
+ - name: Warm with old cache
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ uses: actions/cache/restore@v4
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd compiler install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Save cache
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ uses: actions/cache/save@v4
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
+
+ # ----- FLOW -----
+ discover_flow_inline_configs:
+ name: Discover flow inline configs
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.set-matrix.outputs.result }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/github-script@v7
+ id: set-matrix
+ with:
+ script: |
+ const inlinedHostConfigs = require('./scripts/shared/inlinedHostConfigs.js');
+ return inlinedHostConfigs.map(config => config.shortName);
+
+ flow:
+ name: Flow check ${{ matrix.flow_inline_config_shortname }}
+ needs: [discover_flow_inline_configs, runtime_node_modules_cache]
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
+
+ # ----- FIZZ -----
+ check_generated_fizz_runtime:
+ name: Confirm generated inline Fizz runtime is up to date
+ needs: [runtime_node_modules_cache]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: |
+ yarn generate-inline-fizz-runtime
+ git diff --exit-code || (echo "There was a change to the Fizz runtime. Run \`yarn generate-inline-fizz-runtime\` and check in the result." && false)
+
+ # ----- FEATURE FLAGS -----
+ flags:
+ name: Check flags
+ needs: [runtime_node_modules_cache]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn flags
+
+ # ----- TESTS -----
+ test:
+ name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
+ needs: [runtime_compiler_node_modules_cache]
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ params:
+ - "-r=stable --env=development"
+ - "-r=stable --env=production"
+ - "-r=experimental --env=development"
+ - "-r=experimental --env=production"
+ - "-r=www-classic --env=development --variant=false"
+ - "-r=www-classic --env=production --variant=false"
+ - "-r=www-classic --env=development --variant=true"
+ - "-r=www-classic --env=production --variant=true"
+ - "-r=www-modern --env=development --variant=false"
+ - "-r=www-modern --env=production --variant=false"
+ - "-r=www-modern --env=development --variant=true"
+ - "-r=www-modern --env=production --variant=true"
+ - "-r=xplat --env=development --variant=false"
+ - "-r=xplat --env=development --variant=true"
+ - "-r=xplat --env=production --variant=false"
+ - "-r=xplat --env=production --variant=true"
+ # TODO: Test more persistent configurations?
+ - "-r=stable --env=development --persistent"
+ - "-r=experimental --env=development --persistent"
+ shard:
+ - 1/5
+ - 2/5
+ - 3/5
+ - 4/5
+ - 5/5
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: |
+ yarn.lock
+ compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd compiler install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: node --version
+ - run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
+
+ # Hardcoded to improve parallelism
+ test-linter:
+ name: Test eslint-plugin-react-hooks
+ needs: [runtime_compiler_node_modules_cache]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: |
+ yarn.lock
+ compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
+ - name: Install runtime dependencies
+ run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Install compiler dependencies
+ run: yarn install --frozen-lockfile
+ working-directory: compiler
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: ./scripts/react-compiler/build-compiler.sh && ./scripts/react-compiler/link-compiler.sh
+ - run: yarn workspace eslint-plugin-react-hooks test
+
+ # ----- BUILD -----
+ build_and_lint:
+ name: yarn build and lint
+ needs: [runtime_compiler_node_modules_cache]
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ # yml is dumb. update the --total arg to yarn build if you change the number of workers
+ worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
+ release_channel: [stable, experimental]
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: |
+ yarn.lock
+ compiler/yarn.lock
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: 11.0.22
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd compiler install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn build --index=${{ matrix.worker_id }} --total=25 --r=${{ matrix.release_channel }} --ci
+ env:
+ CI: github
+ RELEASE_CHANNEL: ${{ matrix.release_channel }}
+ NODE_INDEX: ${{ matrix.worker_id }}
+ - name: Lint build
+ run: yarn lint-build
+ - name: Display structure of build
+ run: ls -R build
+ - name: Archive build
+ uses: actions/upload-artifact@v4
+ with:
+ name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
+ path: build
+ if-no-files-found: error
+
+ test_build:
+ name: yarn test-build
+ needs: [build_and_lint, runtime_compiler_node_modules_cache]
+ strategy:
+ fail-fast: false
+ matrix:
+ test_params: [
+ # Intentionally passing these as strings instead of creating a
+ # separate parameter per CLI argument, since it's easier to
+ # control/see which combinations we want to run.
+ -r=stable --env=development,
+ -r=stable --env=production,
+ -r=experimental --env=development,
+ -r=experimental --env=production,
+
+ # TODO: Update test config to support www build tests
+ # - "-r=www-classic --env=development --variant=false"
+ # - "-r=www-classic --env=production --variant=false"
+ # - "-r=www-classic --env=development --variant=true"
+ # - "-r=www-classic --env=production --variant=true"
+ # - "-r=www-modern --env=development --variant=false"
+ # - "-r=www-modern --env=production --variant=false"
+ # - "-r=www-modern --env=development --variant=true"
+ # - "-r=www-modern --env=production --variant=true"
+
+ # TODO: Update test config to support xplat build tests
+ # - "-r=xplat --env=development --variant=false"
+ # - "-r=xplat --env=development --variant=true"
+ # - "-r=xplat --env=production --variant=false"
+ # - "-r=xplat --env=production --variant=true"
+
+ # TODO: Test more persistent configurations?
+ ]
+ shard:
+ - 1/10
+ - 2/10
+ - 3/10
+ - 4/10
+ - 5/10
+ - 6/10
+ - 7/10
+ - 8/10
+ - 9/10
+ - 10/10
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: |
+ yarn.lock
+ compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd compiler install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Display structure of build
+ run: ls -R build
+ - run: node --version
+ - run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci
+
+ test_build_devtools:
+ name: yarn test-build (devtools)
+ needs: [build_and_lint, runtime_node_modules_cache]
+ strategy:
+ fail-fast: false
+ matrix:
+ shard:
+ - 1/5
+ - 2/5
+ - 3/5
+ - 4/5
+ - 5/5
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Display structure of build
+ run: ls -R build
+ - run: node --version
+ - run: yarn test --build --project=devtools -r=experimental --shard=${{ matrix.shard }} --ci
+
+ process_artifacts_combined:
+ name: Process artifacts combined
+ needs: [build_and_lint, runtime_node_modules_cache]
+ permissions:
+ # https://github.com/actions/attest-build-provenance
+ id-token: write
+ attestations: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Display structure of build
+ run: ls -R build
+ - run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
+ - name: Scrape warning messages
+ run: |
+ mkdir -p ./build/__test_utils__
+ node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
+ # Compress build directory into a single tarball for easy download
+ - run: tar -zcvf ./build.tgz ./build
+ # TODO: Migrate scripts to use `build` directory instead of `build2`
+ - run: cp ./build.tgz ./build2.tgz
+ - name: Archive build artifacts
+ id: upload_artifacts_combined
+ uses: actions/upload-artifact@v4
+ with:
+ name: artifacts_combined
+ path: |
+ ./build.tgz
+ ./build2.tgz
+ if-no-files-found: error
+ - uses: actions/attest-build-provenance@v2
+ # We don't verify builds generated from pull requests not originating from facebook/react.
+ # However, if the PR lands, the run on `main` will generate the attestation which can then
+ # be used to download a build via scripts/release/download-experimental-build.js.
+ #
+ # Note that this means that scripts/release/download-experimental-build.js must be run with
+ # --no-verify when downloading a build from a fork.
+ if: github.event_name == 'push' && github.ref_name == 'main' || github.event.pull_request.head.repo.full_name == github.repository
+ with:
+ subject-name: artifacts_combined.zip
+ subject-digest: sha256:${{ steps.upload_artifacts_combined.outputs.artifact-digest }}
+
+ check_error_codes:
+ name: Search build artifacts for unminified errors
+ needs: [build_and_lint, runtime_node_modules_cache]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Display structure of build
+ run: ls -R build
+ - name: Search build artifacts for unminified errors
+ run: |
+ yarn extract-errors
+ git diff --exit-code || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
+
+ check_release_dependencies:
+ name: Check release dependencies
+ needs: [build_and_lint, runtime_node_modules_cache]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Display structure of build
+ run: ls -R build
+ - run: yarn check-release-dependencies
+
+ RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
+ name: Check fixtures DOM (stable)
+ needs: build_and_lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/dom/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn --cwd fixtures/dom install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Display structure of build
+ run: ls -R build
+ - name: Run DOM fixture tests
+ run: |
+ yarn predev
+ yarn test
+ working-directory: fixtures/dom
+ env:
+ RELEASE_CHANNEL: stable
+
+ # ----- FLIGHT -----
+ run_fixtures_flight_tests:
+ name: Run fixtures Flight tests
+ needs: build_and_lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ # Fixture copies some built packages from the workroot after install.
+ # That means dependencies of the built packages are not installed.
+ # We need to install dependencies of the workroot to fulfill all dependency constraints
+ - name: Restore cached node_modules
+ uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/flight/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd fixtures/flight install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Check Playwright version
+ id: playwright_version
+ run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
+ - name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
+ id: cache_playwright_browsers
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
+ - name: Playwright install deps
+ if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
+ working-directory: fixtures/flight
+ run: npx playwright install --with-deps chromium
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Display structure of build
+ run: ls -R build
+ - name: Run tests
+ working-directory: fixtures/flight
+ run: yarn test
+ env:
+ # Otherwise the webserver is a blackbox
+ DEBUG: pw:webserver
+ - name: Archive Flight fixture artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: flight-playwright-report
+ path: fixtures/flight/playwright-report
+ if-no-files-found: warn
+ - name: Archive Flight fixture artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: flight-test-results
+ path: fixtures/flight/test-results
+ if-no-files-found: ignore
+
+ # ----- DEVTOOLS -----
+ build_devtools_and_process_artifacts:
+ name: Build DevTools and process artifacts
+ needs: [build_and_lint, runtime_node_modules_cache]
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ browser: [chrome, firefox, edge]
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - run: ./scripts/ci/pack_and_store_devtools_artifacts.sh ${{ matrix.browser }}
+ env:
+ RELEASE_CHANNEL: experimental
+ - name: Display structure of build
+ run: ls -R build
+ # Simplifies getting the extension for local testing
+ - name: Archive ${{ matrix.browser }} extension
+ uses: actions/upload-artifact@v4
+ with:
+ name: react-devtools-${{ matrix.browser }}-extension
+ path: build/devtools/${{ matrix.browser }}-extension.zip
+ if-no-files-found: error
+ - name: Archive ${{ matrix.browser }} metadata
+ uses: actions/upload-artifact@v4
+ with:
+ name: react-devtools-${{ matrix.browser }}-metadata
+ path: build/devtools/webpack-stats.*.json
+
+ merge_devtools_artifacts:
+ name: Merge DevTools artifacts
+ needs: build_devtools_and_process_artifacts
+ runs-on: ubuntu-latest
+ steps:
+ - name: Merge artifacts
+ uses: actions/upload-artifact/merge@v4
+ with:
+ name: react-devtools
+ pattern: react-devtools-*
+
+ run_devtools_e2e_tests:
+ name: Run DevTools e2e tests
+ needs: [build_and_lint, runtime_node_modules_cache]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache/restore@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ # Don't use restore-keys here. Otherwise the cache grows indefinitely.
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Restore archived build
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Check Playwright version
+ id: playwright_version
+ run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
+ - name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
+ id: cache_playwright_browsers
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
+ - name: Playwright install deps
+ if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
+ run: npx playwright install --with-deps chromium
+ - run: ./scripts/ci/run_devtools_e2e_tests.js
+ env:
+ RELEASE_CHANNEL: experimental
+ - name: Archive Playwright report
+ uses: actions/upload-artifact@v4
+ with:
+ name: devtools-playwright-artifacts
+ path: tmp/playwright-artifacts
+ if-no-files-found: warn
+
+ # ----- SIZEBOT -----
+ sizebot:
+ if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
+ name: Run sizebot
+ needs: [build_and_lint]
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd scripts/release install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Download artifacts for base revision
+ # The build could have been generated from a fork, so we must download the build without
+ # any verification. This is safe since we only use this for sizebot calculation and the
+ # unverified artifact is not used. Additionally this workflow runs in the pull_request
+ # trigger so only restricted permissions are available.
+ run: |
+ GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }}) ${{ (github.event.pull_request.head.repo.full_name != github.repository && '--noVerify') || ''}}
+ mv ./build ./base-build
+ - name: Delete extraneous files
+ # TODO: The `download-experimental-build` script copies the npm
+ # packages into the `node_modules` directory. This is a historical
+ # quirk of how the release script works. Let's pretend they
+ # don't exist.
+ run: rm -rf ./base-build/node_modules
+ - name: Display structure of base-build from origin/main
+ run: ls -R base-build
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - name: Restore archived build for PR
+ uses: actions/download-artifact@v4
+ with:
+ pattern: _build_*
+ path: build
+ merge-multiple: true
+ - name: Scrape warning messages
+ run: |
+ mkdir -p ./build/__test_utils__
+ node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
+ - name: Display structure of build for PR
+ run: ls -R build
+ - run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
+ - run: node ./scripts/tasks/danger
+ - name: Archive sizebot results
+ uses: actions/upload-artifact@v4
+ with:
+ name: sizebot-message
+ path: sizebot-message.md
+ if-no-files-found: ignore
diff --git a/.github/workflows/runtime_commit_artifacts.yml b/.github/workflows/runtime_commit_artifacts.yml
new file mode 100644
index 00000000000..1b98673cd4d
--- /dev/null
+++ b/.github/workflows/runtime_commit_artifacts.yml
@@ -0,0 +1,474 @@
+name: (Runtime) Commit Artifacts for Meta WWW and fbsource V2
+
+on:
+ workflow_run:
+ workflows: ["(Runtime) Build and Test"]
+ types: [completed]
+ branches:
+ - main
+ workflow_dispatch:
+ inputs:
+ commit_sha:
+ required: false
+ type: string
+ force:
+ description: 'Force a commit to the builds/... branches'
+ required: true
+ default: false
+ type: boolean
+ dry_run:
+ description: Perform a dry run (run everything except push)
+ required: true
+ default: false
+ type: boolean
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+jobs:
+ download_artifacts:
+ runs-on: ubuntu-latest
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ steps:
+ - uses: actions/checkout@v4
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd scripts/release install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Download artifacts for base revision
+ run: |
+ GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
+ - name: Display structure of build
+ run: ls -R build
+ - name: Archive build
+ uses: actions/upload-artifact@v4
+ with:
+ name: build
+ path: build/
+ if-no-files-found: error
+
+
+ process_artifacts:
+ runs-on: ubuntu-latest
+ needs: [download_artifacts]
+ outputs:
+ www_branch_count: ${{ steps.check_branches.outputs.www_branch_count }}
+ fbsource_branch_count: ${{ steps.check_branches.outputs.fbsource_branch_count }}
+ last_version_classic: ${{ steps.get_last_version_www.outputs.last_version_classic }}
+ last_version_modern: ${{ steps.get_last_version_www.outputs.last_version_modern }}
+ last_version_rn: ${{ steps.get_last_version_rn.outputs.last_version_rn }}
+ current_version_classic: ${{ steps.get_current_version.outputs.current_version_classic }}
+ current_version_modern: ${{ steps.get_current_version.outputs.current_version_modern }}
+ current_version_rn: ${{ steps.get_current_version.outputs.current_version_rn }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: builds/facebook-www
+ - name: "Get last version string for www"
+ id: get_last_version_www
+ run: |
+ # Empty checks only needed for backwards compatibility,can remove later.
+ VERSION_CLASSIC=$( [ -f ./compiled/facebook-www/VERSION_CLASSIC ] && cat ./compiled/facebook-www/VERSION_CLASSIC || echo '' )
+ VERSION_MODERN=$( [ -f ./compiled/facebook-www/VERSION_MODERN ] && cat ./compiled/facebook-www/VERSION_MODERN || echo '' )
+ echo "Last classic version is $VERSION_CLASSIC"
+ echo "Last modern version is $VERSION_MODERN"
+ echo "last_version_classic=$VERSION_CLASSIC" >> "$GITHUB_OUTPUT"
+ echo "last_version_modern=$VERSION_MODERN" >> "$GITHUB_OUTPUT"
+ - uses: actions/checkout@v4
+ with:
+ ref: builds/facebook-fbsource
+ - name: "Get last version string for rn"
+ id: get_last_version_rn
+ run: |
+ # Empty checks only needed for backwards compatibility,can remove later.
+ VERSION_NATIVE_FB=$( [ -f ./compiled-rn/VERSION_NATIVE_FB ] && cat ./compiled-rn/VERSION_NATIVE_FB || echo '' )
+ echo "Last rn version is $VERSION_NATIVE_FB"
+ echo "last_version_rn=$VERSION_NATIVE_FB" >> "$GITHUB_OUTPUT"
+ - uses: actions/checkout@v4
+ - name: "Check branches"
+ id: check_branches
+ run: |
+ echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT"
+ echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT"
+ - name: Restore downloaded build
+ uses: actions/download-artifact@v4
+ with:
+ name: build
+ path: build
+ - name: Display structure of build
+ run: ls -R build
+ - name: Strip @license from eslint plugin and react-refresh
+ run: |
+ sed -i -e 's/ @license React*//' \
+ build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
+ build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js
+ - name: Insert @headers into eslint plugin and react-refresh
+ run: |
+ sed -i -e 's/ LICENSE file in the root directory of this source tree./ LICENSE file in the root directory of this source tree.\n *\n * @noformat\n * @nolint\n * @lightSyntaxTransform\n * @preventMunge\n * @oncall react_core/' \
+ build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
+ build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js
+ - name: Move relevant files for React in www into compiled
+ run: |
+ # Move the facebook-www folder into compiled
+ mkdir ./compiled
+ mv build/facebook-www ./compiled
+
+ # Move ReactAllWarnings.js to facebook-www
+ mkdir ./compiled/facebook-www/__test_utils__
+ mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
+
+ # Copy eslint-plugin-react-hooks
+ mkdir ./compiled/eslint-plugin-react-hooks
+ cp build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
+ ./compiled/eslint-plugin-react-hooks/index.js
+
+ # Move unstable_server-external-runtime.js into facebook-www
+ mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
+ ./compiled/facebook-www/unstable_server-external-runtime.js
+
+ # Move react-refresh-babel.development.js into babel-plugin-react-refresh
+ mkdir ./compiled/babel-plugin-react-refresh
+ mv build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js \
+ ./compiled/babel-plugin-react-refresh/index.js
+
+ ls -R ./compiled
+ - name: Move relevant files for React in fbsource into compiled-rn
+ run: |
+ BASE_FOLDER='compiled-rn/facebook-fbsource/xplat/js'
+ mkdir -p ${BASE_FOLDER}/react-native-github/Libraries/Renderer/
+ mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-dom,react-is,react-test-renderer}/
+
+ # Move React Native renderer
+ mv build/react-native/implementations/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
+ mv build/react-native/shims/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
+ mv build/facebook-react-native/scheduler/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/scheduler/
+ mv build/facebook-react-native/react/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react/
+ mv build/facebook-react-native/react-dom/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-dom/
+ mv build/facebook-react-native/react-is/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-is/
+ mv build/facebook-react-native/react-test-renderer/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-test-renderer/
+
+ # Delete the OSS renderers, these are sync'd to RN separately.
+ RENDERER_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/implementations/
+ rm $RENDERER_FOLDER/ReactFabric-{dev,prod,profiling}.js
+
+ # Delete the legacy renderer shim, this is not sync'd and will get deleted in the future.
+ SHIM_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/shims/
+ rm $SHIM_FOLDER/ReactNative.js
+
+ # Copy eslint-plugin-react-hooks
+ # NOTE: This is different from www, here we include the full package
+ # including package.json to include dependencies in fbsource.
+ mkdir "$BASE_FOLDER/tools"
+ cp -r build/oss-experimental/eslint-plugin-react-hooks "$BASE_FOLDER/tools"
+
+ # Move React Native version file
+ mv build/facebook-react-native/VERSION_NATIVE_FB ./compiled-rn/VERSION_NATIVE_FB
+
+ ls -R ./compiled-rn
+ - name: Add REVISION files
+ run: |
+ echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled/facebook-www/REVISION
+ cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
+ echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
+ - name: "Get current version string"
+ id: get_current_version
+ run: |
+ VERSION_CLASSIC=$(cat ./compiled/facebook-www/VERSION_CLASSIC)
+ VERSION_MODERN=$(cat ./compiled/facebook-www/VERSION_MODERN)
+ VERSION_NATIVE_FB=$(cat ./compiled-rn/VERSION_NATIVE_FB)
+ echo "Current classic version is $VERSION_CLASSIC"
+ echo "Current modern version is $VERSION_MODERN"
+ echo "Current rn version is $VERSION_NATIVE_FB"
+ echo "current_version_classic=$VERSION_CLASSIC" >> "$GITHUB_OUTPUT"
+ echo "current_version_modern=$VERSION_MODERN" >> "$GITHUB_OUTPUT"
+ echo "current_version_rn=$VERSION_NATIVE_FB" >> "$GITHUB_OUTPUT"
+ - uses: actions/upload-artifact@v4
+ with:
+ name: compiled
+ path: compiled/
+ if-no-files-found: error
+ - uses: actions/upload-artifact@v4
+ with:
+ name: compiled-rn
+ path: compiled-rn/
+ if-no-files-found: error
+
+ commit_www_artifacts:
+ needs: [download_artifacts, process_artifacts]
+ if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.www_branch_count == '0')
+ runs-on: ubuntu-latest
+ permissions:
+ # Used to push a commit to builds/facebook-www
+ contents: write
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: builds/facebook-www
+ - name: Ensure clean directory
+ run: rm -rf compiled
+ - uses: actions/download-artifact@v4
+ with:
+ name: compiled
+ path: compiled/
+ - name: Revert version changes
+ if: needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != ''
+ env:
+ CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
+ CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
+ LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
+ LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
+ run: |
+ echo "Reverting $CURRENT_VERSION_CLASSIC to $LAST_VERSION_CLASSIC"
+ grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "No files found with $CURRENT_VERSION_CLASSIC"
+ grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_CLASSIC/$LAST_VERSION_CLASSIC/g"
+ grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "Classic version reverted"
+ echo "===================="
+ echo "Reverting $CURRENT_VERSION_MODERN to $LAST_VERSION_MODERN"
+ grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "No files found with $CURRENT_VERSION_MODERN"
+ grep -rl "$CURRENT_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_MODERN/$LAST_VERSION_MODERN/g"
+ grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "Modern version reverted"
+ - name: Check for changes
+ if: inputs.force != true
+ id: check_should_commit
+ run: |
+ echo "Full git status"
+ git add .
+ git status
+ echo "===================="
+ if git status --porcelain | grep -qv '/REVISION'; then
+ echo "Changes detected"
+ echo "===== Changes ====="
+ git --no-pager diff -U0 | grep '^[+-]' | head -n 50
+ echo "==================="
+ echo "should_commit=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "No Changes detected"
+ echo "should_commit=false" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Re-apply version changes
+ if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != '')
+ env:
+ CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
+ CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
+ LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
+ LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
+ run: |
+ echo "Re-applying $LAST_VERSION_CLASSIC to $CURRENT_VERSION_CLASSIC"
+ grep -rl "$LAST_VERSION_CLASSIC" ./compiled || echo "No files found with $LAST_VERSION_CLASSIC"
+ grep -rl "$LAST_VERSION_CLASSIC" ./compiled | xargs -r sed -i -e "s/$LAST_VERSION_CLASSIC/$CURRENT_VERSION_CLASSIC/g"
+ grep -rl "$LAST_VERSION_CLASSIC" ./compiled || echo "Classic version re-applied"
+ echo "===================="
+ echo "Re-applying $LAST_VERSION_MODERN to $CURRENT_VERSION_MODERN"
+ grep -rl "$LAST_VERSION_MODERN" ./compiled || echo "No files found with $LAST_VERSION_MODERN"
+ grep -rl "$LAST_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$LAST_VERSION_MODERN/$CURRENT_VERSION_MODERN/g"
+ grep -rl "$LAST_VERSION_MODERN" ./compiled || echo "Classic version re-applied"
+ - name: Will commit these changes
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ run: |
+ git add .
+ git status
+ - name: Check commit message
+ if: inputs.dry_run
+ run: |
+ git fetch origin --quiet
+ git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
+ - name: Commit changes to branch
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ run: |
+ git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
+ git config --global user.name "${{ github.triggering_actor }}"
+
+ git fetch origin --quiet
+ git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
+ - name: Push changes to branch
+ if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
+ run: git push
+
+ commit_fbsource_artifacts:
+ needs: [download_artifacts, process_artifacts]
+ permissions:
+ # Used to push a commit to builds/facebook-fbsource
+ contents: write
+ if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.fbsource_branch_count == '0')
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: builds/facebook-fbsource
+ - name: Ensure clean directory
+ run: rm -rf compiled-rn
+ - uses: actions/download-artifact@v4
+ with:
+ name: compiled-rn
+ path: compiled-rn/
+ - name: Revert version changes
+ if: needs.process_artifacts.outputs.last_version_rn != ''
+ env:
+ CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
+ LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
+ run: |
+ echo "Reverting $CURRENT_VERSION to $LAST_VERSION"
+ grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "No files found with $CURRENT_VERSION"
+ grep -rl "$CURRENT_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$CURRENT_VERSION/$LAST_VERSION/g"
+ grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "Version reverted"
+ - name: Check for changes
+ if: inputs.force != 'true'
+ id: check_should_commit
+ run: |
+ echo "Full git status"
+ git add .
+ git --no-pager diff -U0 --cached | grep '^[+-]' | head -n 100
+ echo "===================="
+ # Ignore REVISION or lines removing @generated headers.
+ if git diff --cached ':(exclude)*REVISION' ':(exclude)*/eslint-plugin-react-hooks/package.json' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" > /dev/null; then
+ echo "Changes detected"
+ echo "===== Changes ====="
+ git --no-pager diff --cached ':(exclude)*REVISION' ':(exclude)*/eslint-plugin-react-hooks/package.json' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" | head -n 50
+ echo "==================="
+ echo "should_commit=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "No Changes detected"
+ echo "should_commit=false" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Re-apply version changes
+ if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_rn != '')
+ env:
+ CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
+ LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
+ run: |
+ echo "Re-applying $LAST_VERSION to $CURRENT_VERSION"
+ grep -rl "$LAST_VERSION" ./compiled-rn || echo "No files found with $LAST_VERSION"
+ grep -rl "$LAST_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$LAST_VERSION/$CURRENT_VERSION/g"
+ grep -rl "$LAST_VERSION" ./compiled-rn || echo "Version re-applied"
+ - name: Add files for signing
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ run: |
+ echo ":"
+ git add .
+ - name: Signing files
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ // TODO: Move this to a script file.
+ // We currently can't call scripts from the repo because
+ // at this point in the workflow, we're on the compiled
+ // artifact branch (so the scripts don't exist).
+ // We can fix this with a composite action in the main repo.
+ // This script is duplicated above.
+ const fs = require('fs');
+ const crypto = require('crypto');
+ const {execSync} = require('child_process');
+
+ // TODO: when we move this to a script, we can use this from npm.
+ // Copy of signedsource since we can't install deps on this branch.
+ const GENERATED = '@' + 'generated';
+ const NEWTOKEN = '<>';
+ const PATTERN = new RegExp(`${GENERATED} (?:SignedSource<<([a-f0-9]{32})>>)`);
+
+ const TokenNotFoundError = new Error(
+ `SignedSource.signFile(...): Cannot sign file without token: ${NEWTOKEN}`
+ );
+
+ function hash(data, encoding) {
+ const md5sum = crypto.createHash('md5');
+ md5sum.update(data, encoding);
+ return md5sum.digest('hex');
+ }
+
+ const SignedSource = {
+ getSigningToken() {
+ return `${GENERATED} ${NEWTOKEN}`;
+ },
+ isSigned(data) {
+ return PATTERN.exec(data) != null;
+ },
+ signFile(data) {
+ if (!data.includes(NEWTOKEN)) {
+ if (SignedSource.isSigned(data)) {
+ // Signing a file that was previously signed.
+ data = data.replace(PATTERN, SignedSource.getSigningToken());
+ } else {
+ throw TokenNotFoundError;
+ }
+ }
+ return data.replace(NEWTOKEN, `SignedSource<<${hash(data, 'utf8')}>>`);
+ },
+ };
+
+ const directory = './compiled-rn';
+ console.log('Signing files in directory:', directory);
+ try {
+ const result = execSync(`git status --porcelain ${directory}`, {encoding: 'utf8'});
+ console.log(result);
+
+ // Parse the git status output to get file paths!
+ const files = result.split('\n').filter(file => file.endsWith('.js'));
+
+ if (files.length === 0) {
+ throw new Error(
+ 'git status returned no files to sign. this job should not have run.'
+ );
+ } else {
+ files.forEach(line => {
+ let file = null;
+ if (line.startsWith('D ')) {
+ return;
+ } else if (line.startsWith('R ')) {
+ file = line.slice(line.indexOf('->') + 3);
+ } else {
+ file = line.slice(3).trim();
+ }
+ if (file) {
+ console.log(' Signing file:', file);
+ const originalContents = fs.readFileSync(file, 'utf8');
+ const signedContents = SignedSource.signFile(
+ originalContents
+ // Need to add the header in, since it's not inserted at build time.
+ .replace(' */\n', ` * ${SignedSource.getSigningToken()}\n */\n`)
+ );
+
+ fs.writeFileSync(file, signedContents, 'utf8');
+ }
+ });
+ }
+ } catch (e) {
+ process.exitCode = 1;
+ console.error('Error signing files:', e);
+ }
+ - name: Will commit these changes
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ run: |
+ git add .
+ git status
+ - name: Check commit message
+ if: inputs.dry_run
+ run: |
+ git fetch origin --quiet
+ git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
+ - name: Commit changes to branch
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ run: |
+ git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
+ git config --global user.name "${{ github.triggering_actor }}"
+
+ git fetch origin --quiet
+ git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
+ - name: Push changes to branch
+ if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
+ run: git push
diff --git a/.github/workflows/runtime_discord_notify.yml b/.github/workflows/runtime_discord_notify.yml
new file mode 100644
index 00000000000..ae9930adf11
--- /dev/null
+++ b/.github/workflows/runtime_discord_notify.yml
@@ -0,0 +1,51 @@
+name: (Runtime) Discord Notify
+
+on:
+ pull_request_target:
+ types: [opened, ready_for_review]
+ paths-ignore:
+ - packages/react-devtools**
+ - compiler/**
+ - .github/workflows/compiler_**.yml
+ - .github/workflows/devtools**.yml
+
+permissions: {}
+
+jobs:
+ check_access:
+ if: ${{ github.event.pull_request.draft == false }}
+ runs-on: ubuntu-latest
+ outputs:
+ is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
+ steps:
+ - run: echo ${{ github.event.pull_request.author_association }}
+ - name: Check is member or collaborator
+ id: check_is_member_or_collaborator
+ if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
+ run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
+
+ check_maintainer:
+ if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
+ needs: [check_access]
+ uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
+ permissions:
+ # Used by check_maintainer
+ contents: read
+ with:
+ actor: ${{ github.event.pull_request.user.login }}
+
+ notify:
+ if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
+ needs: check_maintainer
+ runs-on: ubuntu-latest
+ steps:
+ - name: Discord Webhook Action
+ uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
+ with:
+ webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
+ embed-author-name: ${{ github.event.pull_request.user.login }}
+ embed-author-url: ${{ github.event.pull_request.user.html_url }}
+ embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
+ embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
+ embed-description: ${{ github.event.pull_request.body }}
+ embed-url: ${{ github.event.pull_request.html_url }}
diff --git a/.github/workflows/runtime_eslint_plugin_e2e.yml b/.github/workflows/runtime_eslint_plugin_e2e.yml
new file mode 100644
index 00000000000..92921646c1b
--- /dev/null
+++ b/.github/workflows/runtime_eslint_plugin_e2e.yml
@@ -0,0 +1,65 @@
+name: (Runtime) ESLint Plugin E2E
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ paths-ignore:
+ - compiler/**
+
+permissions: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ # ----- TESTS -----
+ test:
+ name: ESLint v${{ matrix.eslint_major }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ eslint_major:
+ - "6"
+ - "7"
+ - "8"
+ - "9"
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: |
+ yarn.lock
+ compiler/yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock', 'fixtures/eslint-v*/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd compiler install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Install fixture dependencies
+ working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
+ run: yarn --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - name: Build plugin
+ working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
+ run: node build.mjs
+ - name: Run lint test
+ working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
+ run: yarn lint
diff --git a/.github/workflows/runtime_fuzz_tests.yml b/.github/workflows/runtime_fuzz_tests.yml
new file mode 100644
index 00000000000..a88ce523a62
--- /dev/null
+++ b/.github/workflows/runtime_fuzz_tests.yml
@@ -0,0 +1,33 @@
+name: (Runtime) Fuzz tests
+
+on:
+ schedule:
+ - cron: 0 * * * *
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ test_fuzz:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4.1.0
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'yarn'
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+ env:
+ ELECTRON_SKIP_BINARY_DOWNLOAD: "1"
+ shell: bash
+ - name: Run fuzz tests
+ run: |-
+ FUZZ_TEST_SEED=$RANDOM yarn test fuzz --ci
+ FUZZ_TEST_SEED=$RANDOM yarn test --prod fuzz --ci
diff --git a/.github/workflows/runtime_prereleases.yml b/.github/workflows/runtime_prereleases.yml
new file mode 100644
index 00000000000..6559b144997
--- /dev/null
+++ b/.github/workflows/runtime_prereleases.yml
@@ -0,0 +1,110 @@
+name: (Runtime) Publish Prereleases
+
+on:
+ workflow_call:
+ inputs:
+ commit_sha:
+ required: true
+ default: ''
+ type: string
+ release_channel:
+ required: true
+ type: string
+ dist_tag:
+ required: true
+ type: string
+ enableFailureNotification:
+ description: 'Whether to notify the team on Discord when the release fails. Useful if this workflow is called from an automation.'
+ required: false
+ type: boolean
+ only_packages:
+ description: Packages to publish (space separated)
+ type: string
+ skip_packages:
+ description: Packages to NOT publish (space separated)
+ type: string
+ dry:
+ required: true
+ description: Dry run instead of publish?
+ type: boolean
+ default: true
+ secrets:
+ DISCORD_WEBHOOK_URL:
+ description: 'Discord webhook URL to notify on failure. Only required if enableFailureNotification is true.'
+ required: false
+ GH_TOKEN:
+ required: true
+ NPM_TOKEN:
+ required: true
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+jobs:
+ publish_prerelease:
+ name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
+ runs-on: ubuntu-latest
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd scripts/release install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: cp ./scripts/release/ci-npmrc ~/.npmrc
+ - run: |
+ GH_TOKEN=${{ secrets.GH_TOKEN }} scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
+ - name: Check prepared files
+ run: ls -R build/node_modules
+ - if: '${{ inputs.only_packages }}'
+ name: 'Publish ${{ inputs.only_packages }}'
+ run: |
+ scripts/release/publish.js \
+ --ci \
+ --tags=${{ inputs.dist_tag }} \
+ --onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
+ ${{ inputs.dry && '--dry' || '' }}
+ - if: '${{ inputs.skip_packages }}'
+ name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
+ run: |
+ scripts/release/publish.js \
+ --ci \
+ --tags=${{ inputs.dist_tag }} \
+ --skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
+ ${{ inputs.dry && '--dry' || '' }}
+ - if: '${{ !inputs.skip_packages && !inputs.only_packages }}'
+ name: 'Publish all packages'
+ run: |
+ scripts/release/publish.js \
+ --ci \
+ --tags=${{ inputs.dist_tag }} ${{ (inputs.dry && '') || '\'}}
+ ${{ inputs.dry && '--dry' || '' }}
+ - name: Notify Discord on failure
+ if: failure() && inputs.enableFailureNotification == true
+ uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
+ with:
+ webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
+ embed-author-name: "GitHub Actions"
+ embed-title: '[Runtime] Publish of ${{ inputs.release_channel }}@${{ inputs.dist_tag}} release failed'
+ embed-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
diff --git a/.github/workflows/runtime_prereleases_manual.yml b/.github/workflows/runtime_prereleases_manual.yml
new file mode 100644
index 00000000000..407d931e907
--- /dev/null
+++ b/.github/workflows/runtime_prereleases_manual.yml
@@ -0,0 +1,102 @@
+name: (Runtime) Publish Prereleases Manual
+
+on:
+ workflow_dispatch:
+ inputs:
+ prerelease_commit_sha:
+ required: true
+ only_packages:
+ description: Packages to publish (space separated)
+ type: string
+ skip_packages:
+ description: Packages to NOT publish (space separated)
+ type: string
+ dry:
+ required: true
+ description: Dry run instead of publish?
+ type: boolean
+ default: true
+ experimental_only:
+ type: boolean
+ description: Only publish to the experimental tag
+ default: false
+ force_notify:
+ description: Force a Discord notification?
+ type: boolean
+ default: false
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ notify:
+ if: ${{ inputs.force_notify || inputs.dry == false || inputs.dry == 'false' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Discord Webhook Action
+ uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
+ with:
+ webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
+ embed-author-name: ${{ github.event.sender.login }}
+ embed-author-url: ${{ github.event.sender.html_url }}
+ embed-author-icon-url: ${{ github.event.sender.avatar_url }}
+ embed-title: "โ ๏ธ Publishing ${{ inputs.experimental_only && 'EXPERIMENTAL' || 'CANARY & EXPERIMENTAL' }} release ${{ (inputs.dry && ' (dry run)') || '' }}"
+ embed-description: |
+ ```json
+ ${{ toJson(inputs) }}
+ ```
+ embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }}
+
+ publish_prerelease_canary:
+ if: ${{ !inputs.experimental_only }}
+ name: Publish to Canary channel
+ uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ with:
+ commit_sha: ${{ inputs.prerelease_commit_sha }}
+ release_channel: stable
+ # The tags to use when publishing canaries. The main one we should
+ # always include is "canary" but we can use multiple (e.g. alpha,
+ # beta, rc). To declare multiple, use a comma-separated string, like
+ # this:
+ # dist_tag: "canary,alpha,beta,rc"
+ #
+ # TODO: We currently tag canaries with "next" in addition to "canary"
+ # because this used to be called the "next" channel and some
+ # downstream consumers might still expect that tag. We can remove this
+ # after some time has elapsed and the change has been communicated.
+ dist_tag: canary,next
+ only_packages: ${{ inputs.only_packages }}
+ skip_packages: ${{ inputs.skip_packages }}
+ dry: ${{ inputs.dry }}
+ secrets:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ publish_prerelease_experimental:
+ name: Publish to Experimental channel
+ uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ # NOTE: Intentionally running these jobs sequentially because npm
+ # will sometimes fail if you try to concurrently publish two
+ # different versions of the same package, even if they use different
+ # dist tags.
+ needs: publish_prerelease_canary
+ # Ensures the job runs even if canary is skipped
+ if: always()
+ with:
+ commit_sha: ${{ inputs.prerelease_commit_sha }}
+ release_channel: experimental
+ dist_tag: experimental
+ only_packages: ${{ inputs.only_packages }}
+ skip_packages: ${{ inputs.skip_packages }}
+ dry: ${{ inputs.dry }}
+ secrets:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/runtime_prereleases_nightly.yml b/.github/workflows/runtime_prereleases_nightly.yml
new file mode 100644
index 00000000000..f13a92e46f4
--- /dev/null
+++ b/.github/workflows/runtime_prereleases_nightly.yml
@@ -0,0 +1,51 @@
+name: (Runtime) Publish Prereleases Nightly
+
+on:
+ schedule:
+ # At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
+ - cron: 10 16 * * 1,2,3,4,5
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ publish_prerelease_canary:
+ name: Publish to Canary channel
+ uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ with:
+ commit_sha: ${{ github.sha }}
+ release_channel: stable
+ dist_tag: canary,next
+ enableFailureNotification: true
+ dry: false
+ secrets:
+ DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ publish_prerelease_experimental:
+ name: Publish to Experimental channel
+ uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
+ permissions:
+ # We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
+ actions: read
+ # NOTE: Intentionally running these jobs sequentially because npm
+ # will sometimes fail if you try to concurrently publish two
+ # different versions of the same package, even if they use different
+ # dist tags.
+ needs: publish_prerelease_canary
+ with:
+ commit_sha: ${{ github.sha }}
+ release_channel: experimental
+ dist_tag: experimental
+ enableFailureNotification: true
+ dry: false
+ secrets:
+ DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/runtime_releases_from_npm_manual.yml b/.github/workflows/runtime_releases_from_npm_manual.yml
new file mode 100644
index 00000000000..f164e9f0806
--- /dev/null
+++ b/.github/workflows/runtime_releases_from_npm_manual.yml
@@ -0,0 +1,128 @@
+name: (Runtime) Publish Releases from NPM Manual
+
+on:
+ workflow_dispatch:
+ inputs:
+ version_to_promote:
+ required: true
+ description: Current npm version (non-experimental) to promote
+ type: string
+ version_to_publish:
+ required: true
+ description: Version to publish for the specified packages
+ type: string
+ only_packages:
+ description: Packages to publish (space separated)
+ type: string
+ skip_packages:
+ description: Packages to NOT publish (space separated)
+ type: string
+ tags:
+ description: NPM tags (space separated)
+ type: string
+ default: untagged
+ dry:
+ required: true
+ description: Dry run instead of publish?
+ type: boolean
+ default: true
+ force_notify:
+ description: Force a Discord notification?
+ type: boolean
+ default: false
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+
+jobs:
+ notify:
+ if: ${{ inputs.force_notify || inputs.dry == false || inputs.dry == 'false' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Discord Webhook Action
+ uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
+ with:
+ webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
+ embed-author-name: ${{ github.event.sender.login }}
+ embed-author-url: ${{ github.event.sender.html_url }}
+ embed-author-icon-url: ${{ github.event.sender.avatar_url }}
+ embed-title: "โ ๏ธ Publishing release from NPM${{ (inputs.dry && ' (dry run)') || '' }}"
+ embed-description: |
+ ```json
+ ${{ toJson(inputs) }}
+ ```
+ embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }}
+
+ publish:
+ name: Publish releases
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn --cwd scripts/release install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: cp ./scripts/release/ci-npmrc ~/.npmrc
+ - if: '${{ inputs.only_packages }}'
+ name: 'Prepare ${{ inputs.only_packages }} from NPM'
+ run: |
+ scripts/release/prepare-release-from-npm.js \
+ --ci \
+ --skipTests \
+ --version=${{ inputs.version_to_promote }} \
+ --publishVersion=${{ inputs.version_to_publish }} \
+ --onlyPackages=${{ inputs.only_packages }}
+ - if: '${{ inputs.skip_packages }}'
+ name: 'Prepare all packages EXCEPT ${{ inputs.skip_packages }} from NPM'
+ run: |
+ scripts/release/prepare-release-from-npm.js \
+ --ci \
+ --skipTests \
+ --version=${{ inputs.version_to_promote }} \
+ --publishVersion=${{ inputs.version_to_publish }} \
+ --skipPackages=${{ inputs.skip_packages }}
+ - name: Check prepared files
+ run: ls -R build/node_modules
+ - if: '${{ inputs.only_packages }}'
+ name: 'Publish ${{ inputs.only_packages }}'
+ run: |
+ scripts/release/publish.js \
+ --ci \
+ --tags=${{ inputs.tags }} \
+ --publishVersion=${{ inputs.version_to_publish }} \
+ --onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
+ ${{ inputs.dry && '--dry' || '' }}
+ - if: '${{ inputs.skip_packages }}'
+ name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
+ run: |
+ scripts/release/publish.js \
+ --ci \
+ --tags=${{ inputs.tags }} \
+ --publishVersion=${{ inputs.version_to_publish }} \
+ --skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
+ ${{ inputs.dry && '--dry' || '' }}
+ - name: Archive released package for debugging
+ uses: actions/upload-artifact@v4
+ with:
+ name: build
+ path: |
+ ./build/node_modules
diff --git a/.github/workflows/shared_check_maintainer.yml b/.github/workflows/shared_check_maintainer.yml
new file mode 100644
index 00000000000..f6eb9b9a6d1
--- /dev/null
+++ b/.github/workflows/shared_check_maintainer.yml
@@ -0,0 +1,58 @@
+name: (Shared) Check maintainer
+
+on:
+ workflow_call:
+ inputs:
+ actor:
+ required: true
+ type: string
+ outputs:
+ is_core_team:
+ value: ${{ jobs.check_maintainer.outputs.is_core_team }}
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+jobs:
+ check_maintainer:
+ runs-on: ubuntu-latest
+ permissions:
+ # We fetch the contents of the MAINTAINERS file
+ contents: read
+ outputs:
+ is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }}
+ steps:
+ - name: Check if actor is maintainer
+ id: check_if_actor_is_maintainer
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const actor = '${{ inputs.actor }}';
+ const res = await github.rest.repos.getContent({
+ owner: 'facebook',
+ repo: 'react',
+ path: 'MAINTAINERS',
+ ref: 'main',
+ headers: { Accept: 'application/vnd.github+json' }
+ });
+ if (res.status !== 200) {
+ console.error(res);
+ throw new Error('Unable to fetch MAINTAINERS file');
+ }
+ content = Buffer.from(res.data.content, 'base64').toString();
+ if (content == null || typeof content !== 'string') {
+ throw new Error('Unable to retrieve MAINTAINERS file');
+ }
+
+ const maintainers = new Set(content.split('\n'));
+ if (maintainers.has(actor)) {
+ console.log(`๐ข ${actor} is a maintainer`);
+ return true;
+ }
+ console.log(`๐ด ${actor} is NOT a maintainer`);
+ return null;
diff --git a/.github/workflows/shared_cleanup_merged_branch_caches.yml b/.github/workflows/shared_cleanup_merged_branch_caches.yml
new file mode 100644
index 00000000000..ed80a505e42
--- /dev/null
+++ b/.github/workflows/shared_cleanup_merged_branch_caches.yml
@@ -0,0 +1,41 @@
+# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
+
+name: (Shared) Cleanup Merged Branch Caches
+on:
+ pull_request:
+ types:
+ - closed
+ workflow_dispatch:
+ inputs:
+ pr_number:
+ required: true
+ type: string
+
+permissions: {}
+
+jobs:
+ cleanup:
+ runs-on: ubuntu-latest
+ permissions:
+ # `actions:write` permission is required to delete caches
+ # See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
+ actions: write
+ contents: read
+ steps:
+ - name: Cleanup
+ run: |
+ echo "Fetching list of cache key"
+ cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id')
+
+ ## Setting this to not fail the workflow while deleting cache keys.
+ set +e
+ for cacheKey in $cacheKeysForPR
+ do
+ gh cache delete $cacheKey
+ echo "Deleting $cacheKey"
+ done
+ echo "Done"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ BRANCH: refs/pull/${{ inputs.pr_number || github.event.pull_request.number }}/merge
diff --git a/.github/workflows/shared_cleanup_stale_branch_caches.yml b/.github/workflows/shared_cleanup_stale_branch_caches.yml
new file mode 100644
index 00000000000..f6c532b485a
--- /dev/null
+++ b/.github/workflows/shared_cleanup_stale_branch_caches.yml
@@ -0,0 +1,36 @@
+# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
+
+name: (Shared) Cleanup Stale Branch Caches
+on:
+ schedule:
+ # Every 6 hours
+ - cron: 0 */6 * * *
+ workflow_dispatch:
+
+permissions: {}
+
+jobs:
+ cleanup:
+ runs-on: ubuntu-latest
+ permissions:
+ # `actions:write` permission is required to delete caches
+ # See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
+ actions: write
+ contents: read
+ steps:
+ - name: Cleanup
+ run: |
+ echo "Fetching list of cache keys"
+ cacheKeysForPR=$(gh cache list --limit 100 --json id,ref --jq '.[] | select(.ref != "refs/heads/main") | .id')
+
+ ## Setting this to not fail the workflow while deleting cache keys.
+ set +e
+ for cacheKey in $cacheKeysForPR
+ do
+ gh cache delete $cacheKey
+ echo "Deleting $cacheKey"
+ done
+ echo "Done"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
diff --git a/.github/workflows/shared_close_direct_sync_branch_prs.yml b/.github/workflows/shared_close_direct_sync_branch_prs.yml
new file mode 100644
index 00000000000..caa4da880b5
--- /dev/null
+++ b/.github/workflows/shared_close_direct_sync_branch_prs.yml
@@ -0,0 +1,43 @@
+name: (Shared) Close Direct Sync Branch PRs
+
+on:
+ pull_request:
+ branches:
+ - 'builds/facebook-**'
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+jobs:
+ close_pr:
+ runs-on: ubuntu-latest
+ permissions:
+ # Used to create a review and close PRs
+ pull-requests: write
+ contents: write
+ steps:
+ - name: Close PR
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+ const pullNumber = ${{ github.event.number }};
+
+ await github.rest.pulls.createReview({
+ owner,
+ repo,
+ pull_number: pullNumber,
+ body: 'Do not land changes to `${{ github.event.pull_request.base.ref }}`. Please re-open your PR targeting `main` instead.',
+ event: 'REQUEST_CHANGES'
+ });
+ await github.rest.pulls.update({
+ owner,
+ repo,
+ pull_number: pullNumber,
+ state: 'closed'
+ });
diff --git a/.github/workflows/shared_label_core_team_prs.yml b/.github/workflows/shared_label_core_team_prs.yml
new file mode 100644
index 00000000000..cc10e87dcc2
--- /dev/null
+++ b/.github/workflows/shared_label_core_team_prs.yml
@@ -0,0 +1,55 @@
+name: (Shared) Label Core Team PRs
+
+on:
+ pull_request_target:
+ types: [opened]
+
+permissions: {}
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+jobs:
+ check_access:
+ runs-on: ubuntu-latest
+ outputs:
+ is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
+ steps:
+ - run: echo ${{ github.event.pull_request.author_association }}
+ - name: Check is member or collaborator
+ id: check_is_member_or_collaborator
+ if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
+ run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
+
+ check_maintainer:
+ if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
+ needs: [check_access]
+ uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
+ permissions:
+ # Used by check_maintainer
+ contents: read
+ with:
+ actor: ${{ github.event.pull_request.user.login }}
+
+ label:
+ if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
+ runs-on: ubuntu-latest
+ needs: check_maintainer
+ permissions:
+ # Used to add labels on issues
+ issues: write
+ # Used to add labels on PRs
+ pull-requests: write
+ steps:
+ - name: Label PR as React Core Team
+ uses: actions/github-script@v7
+ with:
+ script: |
+ github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: ${{ github.event.number }},
+ labels: ['React Core Team']
+ });
diff --git a/.github/workflows/shared_lint.yml b/.github/workflows/shared_lint.yml
new file mode 100644
index 00000000000..3c359cff228
--- /dev/null
+++ b/.github/workflows/shared_lint.yml
@@ -0,0 +1,110 @@
+name: (Shared) Lint
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+ # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
+ SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
+
+jobs:
+ prettier:
+ name: Run prettier
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: yarn prettier-check
+
+ eslint:
+ name: Run eslint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: node ./scripts/tasks/eslint
+
+ check_license:
+ name: Check license
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: ./scripts/ci/check_license.sh
+
+ test_print_warnings:
+ name: Test print warnings
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+ cache-dependency-path: yarn.lock
+ - name: Restore cached node_modules
+ uses: actions/cache@v4
+ id: node_modules
+ with:
+ path: |
+ **/node_modules
+ key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
+ - run: yarn install --frozen-lockfile
+ if: steps.node_modules.outputs.cache-hit != 'true'
+ - run: ./scripts/ci/test_print_warnings.sh
diff --git a/.github/workflows/shared_stale.yml b/.github/workflows/shared_stale.yml
new file mode 100644
index 00000000000..c24895edc5d
--- /dev/null
+++ b/.github/workflows/shared_stale.yml
@@ -0,0 +1,55 @@
+# Configuration for stale action workflow - https://github.com/actions/stale
+name: (Shared) Manage stale issues and PRs
+on:
+ schedule:
+ # Run hourly
+ - cron: '0 * * * *'
+ workflow_dispatch:
+
+permissions:
+ # https://github.com/actions/stale/tree/v9/?tab=readme-ov-file#recommended-permissions
+ issues: write
+ pull-requests: write
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v9
+ with:
+ # --- Issues & PRs ---
+ # Number of days of inactivity before an issue or PR becomes stale
+ days-before-stale: 90
+ # Number of days of inactivity before a stale issue or PR is closed
+ days-before-close: 7
+ # API calls per run
+ operations-per-run: 100
+
+ # --- Issues ---
+ stale-issue-label: "Resolution: Stale"
+ # Comment to post when marking an issue as stale
+ stale-issue-message: >
+ This issue has been automatically marked as stale.
+ **If this issue is still affecting you, please leave any comment** (for example, "bump"), and we'll keep it open.
+ We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
+ # Comment to post when closing a stale issue
+ close-issue-message: >
+ Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!
+ # Issues with these labels will never be considered stale
+ exempt-issue-labels: "Partner,React Core Team,Resolution: Backlog,Type: Bug,Type: Discussion,Type: Needs Investigation,Type: Regression,Type: Feature Request,Type: Enhancement"
+
+ # --- PRs ---
+ stale-pr-label: "Resolution: Stale"
+ # Comment to post when marking a pull request as stale
+ stale-pr-message: >
+ This pull request has been automatically marked as stale.
+ **If this pull request is still relevant, please leave any comment** (for example, "bump"), and we'll keep it open.
+ We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.
+ # Comment to post when closing a stale pull request
+ close-pr-message: >
+ Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!
+ # PRs with these labels will never be considered stale
+ exempt-pr-labels: "Partner,React Core Team,Resolution: Backlog,Type: Bug,Type: Discussion,Type: Needs Investigation,Type: Regression,Type: Feature Request,Type: Enhancement"
diff --git a/.gitignore b/.gitignore
index a036ec3a6be..438d125b47a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,21 +1,43 @@
.DS_STORE
node_modules
+scripts/flow/*/.flowconfig
+.flowconfig
*~
*.pyc
-static
.grunt
_SpecRunner.html
__benchmarks__
build/
+remote-repo/
+coverage/
.module-cache
-*.gem
-docs/code
-docs/_site
-docs/.sass-cache
-docs/css/react.css
-docs/js/*
-docs/downloads
-examples/shared/*.js
+fixtures/dom/public/react-dom.js
+fixtures/dom/public/react.js
test/the-files-to-test.generated.js
*.log*
chrome-user-data
+*.sublime-project
+*.sublime-workspace
+.idea
+*.iml
+.vscode
+*.swp
+*.swo
+/tmp
+/.worktrees
+.claude/*.local.*
+
+packages/react-devtools-core/dist
+packages/react-devtools-extensions/chrome/build
+packages/react-devtools-extensions/chrome/*.crx
+packages/react-devtools-extensions/chrome/*.pem
+packages/react-devtools-extensions/firefox/build
+packages/react-devtools-extensions/firefox/*.xpi
+packages/react-devtools-extensions/firefox/*.pem
+packages/react-devtools-extensions/shared/build
+packages/react-devtools-extensions/.tempUserDataDir
+packages/react-devtools-fusebox/dist
+packages/react-devtools-inline/dist
+packages/react-devtools-shell/dist
+packages/react-devtools-timeline/dist
+
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index 27c028d0999..00000000000
--- a/.jshintrc
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "node": true,
-
- "boss": true,
- "curly": true,
- "devel": true,
- "eqnull": true,
- "expr": true,
- "funcscope": true,
- "globalstrict": true,
- "loopfunc": true,
- "newcap": false,
- "noempty": true,
- "nonstandard": true,
- "onecase": true,
- "sub": true,
- "regexdash": true,
- "trailing": true,
- "undef": true,
- "unused": "vars"
-}
diff --git a/.mailmap b/.mailmap
index 4d3c4e90796..e661c3707d5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,44 +1,118 @@
+Adam Timberlake
+Alex Mykyta
+Alex Pien
+Alex Pien
+Alex Pien
+Andreas Savvides
+Andreas Savvides
+Andreas Svensson
+Andres Suarez
+Andrew Kulakov
+Andrew Sokolov
+Anto Aravinth
+Baraa Hamodi
+Ben Halpern
Ben Newman
+Benjamin Woodruff
Bill Fisher
+Blaine Kasten
+Brandon Tilley
+Changsoon Bok
Cheng Lou
+Christian Oliff
Christoph Pojer
Christoph Pojer
Connor McSheffrey
+Conor Hastings
Dan Schafer
Daniel Gasienica
Daniel Gasienica
+Daniel Hejl
Daniel Lo Nigro
+Dave Galbraith
+Dennis Johnson
+Dmitry Blues
+Dongsheng Liu
+Erik Harper
Evan Coonrod
Fabio M. Costa
Felix Kling
Franรงois-Xavier Bois
+Fyodor Ivanishchev
+Gabe Levi
Geert Pasteels
George A Sisco III
+Georgii Dolzhykov
Harry Hull
Hendrik Swanepoel
+Hyeock Kwon
+Ian Obermiller
+Ilia Pavlenkov
+Ilyรก Belsky
Ingvar Stepanyan
Irae Carvalho
+Ivan Vergiliev
+JJ Weber
+Jae Hun Ro
Jaime Mingo
James Brantly
+Jan Hancic
Jan Kassens
Jason Bonta
+Jason Quense
Jason Trill
+Jeff Chan
Jeff Morrison
Jeff Morrison
+Jeff Morrison
Jeffrey Lin
+Jim Sproch
+Jim Sproch
+Jim Sproch
+Jinwoo Oh
+Jinxiu Lee
+Jiyeon Seo
+Jon Chester
+Jon Madison
Jonathan Hsu
+Jonathan Persson
Jordan Walke
Jordan Walke
+Joseph Savona
Josh Duck
+Juan Serrano
Jun Wu
+Justin Robison
Keito Uchiyama
+Kevin Coughlin
+Krystian Karczewski
Kunal Mehta
Laurence Rowe
+Lea Rosema
+Marcin K.
+Mark Anderson
+Mark Funk
Martin Andert
Mathieu M-Gosselin
+Matsunoki
+Matt Brookes
+Matt Dunn-Rankin
+Matt Zabriskie
+Matthew Johnston
+Matthew Looi
+Mattijs Kneppers
+Max Heiber
+Max Stoiber
Michal Srb xixixao
+Michelle Todd
+Mihai Parparita
+Minwe LUO
+Murray M. Moss
+Murray M. Moss
+Neri Marschik
Nick Gavalas
Nick Thompson
+Patrick Stapleton
Paul OโShannessy
Paul Shen
Pete Hunt
@@ -48,15 +122,47 @@ Pete Hunt
Petri Lievonen
Petri Lievonen
Pieter Vanderwerff
+Pouja Nikray
+Rainer Oviir
Ray
Richard Feldman
Richard Livesey
+Rick Hanlon
+Rick Hanlon
+Rob Arnold
+Robert Binna
+Robin Frischmann
Sander Spies
+Scott Feeney
Sebastian Markbรฅge
+Sergey Rubanov
+Shogun Sea
+Soichiro Kawamura
+Sophie Alpert
+Sophie Alpert
+Sophie Alpert
+Sophie Alpert
+Sota Ohara
+Steven Luscher
+Steven Luscher
+Steven Luscher
+Steven Luscher
+Seth Webster
Stoyan Stefanov
+Tengfei Guo
Thomas Aylott
Timothy Yung
+Tomoya Suzuki
+Vasiliy Loginevskiy
+Vasiliy Loginevskiy
Vjeux
Vjeux
Volkan Unsal
+Wander Wang
+Xavier Morel
+YouBao Nong
+Yutaka Nakajima
Zach Bruggeman
+iawia002 <850127508@qq.com>
+ๅ
ๅฝฆ