diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json new file mode 100644 index 000000000000..2164efec0c8f --- /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 7734a2a81437..48d2b3d27e85 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 index edf82896f93a..fd9cc6bdca2f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,9 +1,33 @@ -# We can probably lint these later but not important at this point -src/vendor -# eslint uses JSX* node types to determine if using JSX. esprima-fb still uses -# XJS* nodes. When we fix that (https://github.com/facebook/esprima/pull/85) we -# can enable linting the tests and fix those errors. -src/**/__tests__/** -# This should be enabled but that folder has too much in it that doesn't belong -src/test -test/the-files-to-test.generated.js +# 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 b/.eslintrc deleted file mode 100644 index 347734de88ac..000000000000 --- a/.eslintrc +++ /dev/null @@ -1,51 +0,0 @@ ---- -parser: esprima-fb - -env: - browser: true - node: true - -globals: - __DEV__: true - -rules: - # ERRORS - space-before-blocks: 2 - indent: [2, 2, indentSwitchCase: true] - brace-style: 2 - space-after-keywords: 2 - strict: 2 - # We actually have a transform to support this and we fix this for bundled - # releases but not for the npm package, so enforce it strictly - no-comma-dangle: 2 - # Make this a warning for now. We do this in a few places so we might need to - # disable - no-unused-expressions: 2 - block-scoped-var: 2 - eol-last: 2 - dot-notation: 2 - consistent-return: 2 - no-unused-vars: [2, args: none] - quotes: [2, 'single'] - - # WARNINGS - # This is the only one that's hard to track since we don't lint just changes. - max-len: [1, 80] - - # WISHLIST. One day... - # We'll need a custom version of this that does a subset of the whole rule. - # Otherwise this is just too noisy. - # valid-jsdoc: 1 - - # DISABLED. These aren't compatible with our style - # We use this for private/internal variables - no-underscore-dangle: 0 - # We pass constructors around / access them from members - new-cap: 0 - # We do this a lot. - no-use-before-define: 0 - # We do this in a few places to align values - key-spacing: 0 - - # DISABLED. These currently cause errors when running. - no-multi-spaces: 0 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000000..dec7cd8f304c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,657 @@ +'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', + 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 000000000000..2c19b2484595 --- /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 000000000000..e561f9d26154 --- /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 000000000000..a2c0e3d7aa38 --- /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 000000000000..2b1404175e69 --- /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 000000000000..f90209764681 --- /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 000000000000..23fbc65ab576 --- /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 000000000000..11800ad75759 --- /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 000000000000..5a57cf6a32c1 --- /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 000000000000..a19e87e25e78 --- /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 000000000000..76cd3310b1a7 --- /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 000000000000..c4a7a16aca3b --- /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 000000000000..ca2b5589def2 --- /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 000000000000..6a3b52e21ae5 --- /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 000000000000..bb498f003710 --- /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 000000000000..9fe0c55e0bd0 --- /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 000000000000..c1cc8df2eb75 --- /dev/null +++ b/.github/workflows/runtime_build_and_test.yml @@ -0,0 +1,889 @@ +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: 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, + + # Dev Tools + --project=devtools -r=experimental, + + # 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: yarn test --build ${{ matrix.test_params }} --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 000000000000..1b98673cd4dd --- /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 000000000000..ae9930adf114 --- /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 000000000000..92921646c1bc --- /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 000000000000..a88ce523a62f --- /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 000000000000..6559b1449971 --- /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 000000000000..407d931e9073 --- /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 000000000000..f13a92e46f40 --- /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 000000000000..f164e9f08066 --- /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 000000000000..f6eb9b9a6d12 --- /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 000000000000..ed80a505e428 --- /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 000000000000..f6c532b485a4 --- /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 000000000000..caa4da880b5f --- /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 000000000000..cc10e87dcc2c --- /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 000000000000..3c359cff2280 --- /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 000000000000..c24895edc5da --- /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 fac6da38e4f2..d2aefb620263 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,41 @@ .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/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 + +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/.mailmap b/.mailmap index b78d576ff81f..e661c3707d5d 100644 --- a/.mailmap +++ b/.mailmap @@ -1,49 +1,118 @@ +Adam Timberlake +Alex Mykyta +Alex Pien +Alex Pien +Alex Pien +Andreas Savvides +Andreas Savvides +Andreas Svensson Andres Suarez -Ben Alpert -Ben Alpert +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 @@ -53,16 +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> +元彦 +张敏 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000000..5f53e875de68 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.19.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..7e09af76a3af --- /dev/null +++ b/.prettierignore @@ -0,0 +1,41 @@ +# react runtime +build + +packages/react-devtools-core/dist +packages/react-devtools-extensions/chrome/build +packages/react-devtools-extensions/firefox/build +packages/react-devtools-extensions/edge/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 + +# react compiler +compiler/**/dist +compiler/**/__tests__/fixtures/**/*.expect.md +compiler/**/.next + +# contains invalid graphql`...` which results in a promise rejection error from `yarn prettier-all`. +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js + +compiler/crates +compiler/target +compiler/apps/playground/public + +compiler/**/LICENSE +compiler/*.md* +compiler/*.json +compiler/*.css +compiler/*.webmanifest +compiler/*.map +compiler/*.sh +compiler/*.txt +compiler/*.ico +compiler/*.svg +compiler/*.lock +compiler/*.toml diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000000..aa54cbae1f9f --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,34 @@ +'use strict'; + +const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion'); + +module.exports = { + bracketSpacing: false, + singleQuote: true, + bracketSameLine: true, + trailingComma: 'es5', + printWidth: 80, + parser: 'flow', + arrowParens: 'avoid', + overrides: [ + { + files: ['*.code-workspace'], + options: { + parser: 'json-stringify', + }, + }, + { + files: esNextPaths, + options: { + trailingComma: 'all', + }, + }, + { + files: ['*.ts', '*.tsx'], + options: { + trailingComma: 'all', + parser: 'typescript', + }, + }, + ], +}; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8fab1828c2f2..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ ---- -language: node_js -node_js: -- '0.10' -sudo: false -cache: - directories: - - node_modules -before_install: -- | - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - PR_FIRST=$(curl -s https://github.com/${TRAVIS_REPO_SLUG}/pull/${TRAVIS_PULL_REQUEST}.patch | head -1 | grep -o -E '\b[0-9a-f]{40}\b' | tr -d '\n') - TRAVIS_COMMIT_RANGE=$PR_FIRST^..$TRAVIS_COMMIT - fi - git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/' || { - echo "Only docs were updated, stopping build process." - exit - } -script: -- | - grunt $TEST_TYPE -after_script: -- | - if [ "$TEST_TYPE" = test ] && [ "$SERVER" ]; then - grunt build - curl \ - -F "react=@build/react.js" \ - -F "react.min=@build/react.min.js" \ - -F "transformer=@build/JSXTransformer.js" \ - -F "react-with-addons=@build/react-with-addons.js" \ - -F "react-with-addons.min=@build/react-with-addons.min.js" \ - -F "npm-react=@build/react.tgz" \ - -F "npm-react-tools=@build/react-tools.tgz" \ - -F "commit=$TRAVIS_COMMIT" \ - -F "date=`git log --format='%ct' -1`" \ - -F "pull_request=$TRAVIS_PULL_REQUEST" \ - -F "token=$SECRET_TOKEN" \ - -F "branch=$TRAVIS_BRANCH" \ - $SERVER - fi -env: - matrix: - - TEST_TYPE=test - - TEST_TYPE=jest - - TEST_TYPE=lint - - TEST_TYPE=test:webdriver:saucelabs:modern - global: - # SERVER - - secure: qPvsJ46XzGrdIuPA70b55xQNGF8jcK7N1LN5CCQYYocXLa+fBrl+fTE77QvehOPhqwJXcj6kOxI+sY0KrVwV7gmq2XY2HZGWUSCxTN0SZlNIzqPA80Y7G/yOjA4PUt8LKgP+8tptyhTAY56qf+hgW8BoLiKOdztYF2p+3zXOLuA= - # SECRET_TOKEN - - secure: dkpPW+VnoqC/okhRdV90m36NcyBFhcwEKL3bNFExAwi0dXnFao8RoFlvnwiPlA23h2faROkMIetXlti6Aju08BgUFV+f9aL6vLyU7gUent4Nd3413zf2fwDtXIWIETg6uLnOpSykGKgCAT/hY3Q2oPLqOoY0OxfgnbqwxkxljrE= -matrix: - fast_finish: true - allow_failures: - - env: TEST_TYPE=test:coverage - - env: TEST_TYPE=perf:full - - env: TEST_TYPE=test:webdriver:saucelabs:modern - - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie11 - - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie10 - - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie9 - - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie8 - - env: TEST_TYPE=test:webdriver:saucelabs:ios - - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=safari -notifications: - irc: - use_notice: true - skip_join: true - on_success: change - on_failure: change - channels: - - chat.freenode.net#reactjs diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 25c859840fc3..000000000000 --- a/AUTHORS +++ /dev/null @@ -1,243 +0,0 @@ -Adam Krebs -Adam Mark -Adam Solove -Alan deLevie -Alex Zelenskiy -Alexander Solovyov -Alexey Raspopov -Andre Z Sanchez -Andreas Svensson -Andres Suarez -Andrew Davey -Andrew Rasmussen -Andrew Zich -Andrey Popp <8mayday@gmail.com> -Anthony van der Hoorn -Areeb Malik -Ayman Osman -Ben Alpert -Ben Foxall -Ben Newman -Ben Ripkens -Bill Fisher -Bob Eagan -Bojan Mihelac -Brandon Bloom -Brandon Tilley -Brian Cooke -Brian Kim -Brian Reavis -Brian Rue -Cam Spiers -Cassus Adam Banko -Cat Chen -Charles Marsh -Cheng Lou -Chris Sciolla -Christian Alfoni -Christian Roman -Christoph Pojer -Christopher Monsanto -Clay Allsopp -Connor McSheffrey -Cotton Hou -Dan Abramov -Dan Schafer -Daniel Gasienica -Daniel Lo Nigro -Daniel Miladinov -Daniel Schonfeld -Danny Ben-David -Daryl Lau -David Hellsing -David Hu -Devon Blandin -Dmitrii Abramov -Dmitry Mazuro -Dustin Getz -Enguerran -Eric Clemmons -Eric Florenzano -Eric Schoffstall -Evan Coonrod -Fabio M. Costa -Felipe Oliveira Carvalho -Felix Gnass -Felix Kling -Fernando Correia -François-Xavier Bois -Fred Zhao -G Scott Olson -Garren Smith -Geert Pasteels -Geert-Jan Brits -George A Sisco III -Gilbert -Glen Mailer -Greg Hurrell -Greg Roodt -Guangqiang Dong -Guido Bouman -Harry Hull -Harshad Sabne -Hendrik Swanepoel -Hugo Jobling -Ian Obermiller -Ingvar Stepanyan -Irae Carvalho -Isaac Salier-Hellendag -Ivan Kozik -Ivan Vergiliev -Jacob Gable -Jacob Greenleaf -Jaime Mingo -Jakub Malinowski -James Brantly -James Ide -James Seppi -Jamie Wong -Jamison Dance -Jan Hancic -Jan Kassens -Jared Forsyth -Jason Bonta -Jason Trill -Jean Lauliac -Jed Watson -Jeff Barczewski -Jeff Carpenter -Jeff Chan -Jeff Morrison -Jeff Welch -Jeffrey Lin -Jesse Skinner -Jignesh Kakadiya -Jim OBrien -Jimmy Jea -Jing Chen -Johannes Baiter -Johannes Emerich -John Watson -Jon Beebe -Jonas Enlund -Jonas Gebhardt -Jonathan Hsu -Jordan Walke -Josh Bassett -Josh Duck -Josh Yudaken -Joshua Ma -Julen Ruiz Aizpuru -Julien Bordellier -Jun Wu -Juraj Dudak -Justin Jaffray -Karl Mikkelsen -Karpich Dmitry -Keito Uchiyama -Kit Randel -Kunal Mehta -Kyle Mathews -Laurence Rowe -Lee Byron -Levi McCallum -Lily -Logan Allen -Luigy Leon -Marcin Kwiatkowski -Marcin Szczepanski -Mariano Desanze -Mark Anderson -Mark Hintz -Mark IJbema -Mark Richardson -Marshall Roch -Martin Andert -Martin Konicek -Mathieu M-Gosselin -Matt Harrison -Matthew Dapena-Tretter -Matti Nelimarkka -Max F. Albrecht <1@178.is> -Michael Chan -Michael Randers-Pehrson -Michal Srb -Mike D Pilsbury -Miorel Palii -Mouad Debbar -Nadeesha Cabral -Naman Goel -Nate Hunzaker -Nathan White -Nicholas Bergson-Shilcock -Nick Fitzgerald -Nick Gavalas -Nick Merwin -Nick Thompson -Nick Williams -Niklas Boström -Oleg -Oleksii Markhovskyi -Oliver Zeigermann -Owen Coutts -Pablo Lacerda de Miranda -Pascal Hartig -Paul O’Shannessy -Paul Seiffert -Paul Shen -Pete Hunt -Peter Cottle -Petri Lievonen -Pieter Vanderwerff -Rajiv Tirumalareddy -Randall Randall -Ray -Richard D. Worth -Richard Feldman -Richard Livesey -Rick Beerendonk -Rob Arnold -Ryan Seddon -Sahat Yalkabov -Sam Selikoff -Sander Spies -Sean Kinsey -Sebastian Markbåge -Sergey Generalov -Shane O'Sullivan -Shaun Trennery -Sheraz -Shripad K -Simon Højberg -Simon Welsh -Stefan Dombrowski -Stephen Murphy -Stoyan Stefanov -Sundeep Malladi -Sunny Juneja -Sven Helmberger -Sébastien Lorber -Thomas Aylott -Thomas Boyt -Thomas Shaddox -Thomas Shafer -ThomasCrvsr -Timothy Yung -Tom Haggie -Tom Hauburger -Tom MacWright -Tom Occhino -Ville Immonen -Vjeux -Volkan Unsal -Wayne Larsen -WickyNilliams -Wincent Colaiuta -XuefengWu -Yuriy Dybskiy -Yuval Dekel -Zach Bruggeman -cutbko -davidxi -imagentleman -jon madison diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e0343e0287..5953fafc2786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2287 @@ +## 19.2.1 (Dec 3, 2025) + +### React Server Components + +- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277)) + +## 19.2.0 (October 1st, 2025) + +Below is a list of all new features, APIs, and bug fixes. + +Read the [React 19.2 release post](https://react.dev/blog/2025/10/01/react-19-2) for more information. + +### New React Features + +- [``](https://react.dev/reference/react/Activity): A new API to hide and restore the UI and internal state of its children. +- [`useEffectEvent`](https://react.dev/reference/react/useEffectEvent) is a React Hook that lets you extract non-reactive logic into an [Effect Event](https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event). +- [`cacheSignal`](https://react.dev/reference/react/cacheSignal) (for RSCs) lets your know when the `cache()` lifetime is over. +- [React Performance tracks](https://react.dev/reference/dev-tools/react-performance-tracks) appear on the Performance panel’s timeline in your browser developer tools + +### New React DOM Features + +- Added resume APIs for partial pre-rendering with Web Streams: + - [`resume`](https://react.dev/reference/react-dom/server/resume): to resume a prerender to a stream. + - [`resumeAndPrerender`](https://react.dev/reference/react-dom/static/resumeAndPrerender): to resume a prerender to HTML. +- Added resume APIs for partial pre-rendering with Node Streams: + - [`resumeToPipeableStream`](https://react.dev/reference/react-dom/server/resumeToPipeableStream): to resume a prerender to a stream. + - [`resumeAndPrerenderToNodeStream`](https://react.dev/reference/react-dom/static/resumeAndPrerenderToNodeStream): to resume a prerender to HTML. +- Updated [`prerender`](https://react.dev/reference/react-dom/static/prerender) APIs to return a `postponed` state that can be passed to the `resume` APIs. + +### Notable changes + +- React DOM now batches suspense boundary reveals, matching the behavior of client side rendering. This change is especially noticeable when animating the reveal of Suspense boundaries e.g. with the upcoming `` Component. React will batch as much reveals as possible before the first paint while trying to hit popular first-contentful paint metrics. +- Add Node Web Streams (`prerender`, `renderToReadableStream`) to server-side-rendering APIs for Node.js +- Use underscore instead of `:` IDs generated by useId + +### All Changes + +#### React + +- `` was developed over many years, starting before `ClassComponent.setState` (@acdlite @sebmarkbage and many others) +- Stringify context as "SomeContext" instead of "SomeContext.Provider" (@kassens [#33507](https://github.com/facebook/react/pull/33507)) +- Include stack of cause of React instrumentation errors with `%o` placeholder (@eps1lon [#34198](https://github.com/facebook/react/pull/34198)) +- Fix infinite `useDeferredValue` loop in popstate event (@acdlite [#32821](https://github.com/facebook/react/pull/32821)) +- Fix a bug when an initial value was passed to `useDeferredValue` (@acdlite [#34376](https://github.com/facebook/react/pull/34376)) +- Fix a crash when submitting forms with Client Actions (@sebmarkbage [#33055](https://github.com/facebook/react/pull/33055)) +- Hide/unhide the content of dehydrated suspense boundaries if they resuspend (@sebmarkbage [#32900](https://github.com/facebook/react/pull/32900)) +- Avoid stack overflow on wide trees during Hot Reload (@sophiebits [#34145](https://github.com/facebook/react/pull/34145)) +- Improve Owner and Component stacks in various places (@sebmarkbage, @eps1lon: [#33629](https://github.com/facebook/react/pull/33629), [#33724](https://github.com/facebook/react/pull/33724), [#32735](https://github.com/facebook/react/pull/32735), [#33723](https://github.com/facebook/react/pull/33723)) +- Add `cacheSignal` (@sebmarkbage [#33557](https://github.com/facebook/react/pull/33557)) + +#### React DOM + +- Block on Suspensey Fonts during reveal of server-side-rendered content (@sebmarkbage [#33342](https://github.com/facebook/react/pull/33342)) +- Use underscore instead of `:` for IDs generated by `useId` (@sebmarkbage, @eps1lon: [#32001](https://github.com/facebook/react/pull/32001), [https://github.com/facebook/react/pull/33342](https://github.com/facebook/react/pull/33342)[#33099](https://github.com/facebook/react/pull/33099), [#33422](https://github.com/facebook/react/pull/33422)) +- Stop warning when ARIA 1.3 attributes are used (@Abdul-Omira [#34264](https://github.com/facebook/react/pull/34264)) +- Allow `nonce` to be used on hoistable styles (@Andarist [#32461](https://github.com/facebook/react/pull/32461)) +- Warn for using a React owned node as a Container if it also has text content (@sebmarkbage [#32774](https://github.com/facebook/react/pull/32774)) +- s/HTML/text for for error messages if text hydration mismatches (@rickhanlonii [#32763](https://github.com/facebook/react/pull/32763)) +- Fix a bug with `React.use` inside `React.lazy`\-ed Component (@hi-ogawa [#33941](https://github.com/facebook/react/pull/33941)) +- Enable the `progressiveChunkSize` option for server-side-rendering APIs (@sebmarkbage [#33027](https://github.com/facebook/react/pull/33027)) +- Fix a bug with deeply nested Suspense inside Suspense fallback when server-side-rendering (@gnoff [#33467](https://github.com/facebook/react/pull/33467)) +- Avoid hanging when suspending after aborting while rendering (@gnoff [#34192](https://github.com/facebook/react/pull/34192)) +- Add Node Web Streams to server-side-rendering APIs for Node.js (@sebmarkbage [#33475](https://github.com/facebook/react/pull/33475)) + +#### React Server Components + +- Preload `` and `` using hints before they're rendered (@sebmarkbage [#34604](https://github.com/facebook/react/pull/34604)) +- Log error if production elements are rendered during development (@eps1lon [#34189](https://github.com/facebook/react/pull/34189)) +- Fix a bug when returning a Temporary reference (e.g. a Client Reference) from Server Functions (@sebmarkbage [#34084](https://github.com/facebook/react/pull/34084), @denk0403 [#33761](https://github.com/facebook/react/pull/33761)) +- Pass line/column to `filterStackFrame` (@eps1lon [#33707](https://github.com/facebook/react/pull/33707)) +- Support Async Modules in Turbopack Server References (@lubieowoce [#34531](https://github.com/facebook/react/pull/34531)) +- Add support for .mjs file extension in Webpack (@jennyscript [#33028](https://github.com/facebook/react/pull/33028)) +- Fix a wrong missing key warning (@unstubbable [#34350](https://github.com/facebook/react/pull/34350)) +- Make console log resolve in predictable order (@sebmarkbage [#33665](https://github.com/facebook/react/pull/33665)) + +#### React Reconciler + +- [createContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L255-L261) and [createHydrationContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L305-L312) had their parameter order adjusted after `on*` handlers to account for upcoming experimental APIs + +## 19.1.2 (Dec 3, 2025) + +### React Server Components + +- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277)) + +## 19.1.1 (July 28, 2025) + +### React +* Fixed Owner Stacks to work with ES2015 function.name semantics ([#33680](https://github.com/facebook/react/pull/33680) by @hoxyq) + +## 19.1.0 (March 28, 2025) + +### Owner Stack + +An Owner Stack is a string representing the components that are directly responsible for rendering a particular component. You can log Owner Stacks when debugging or use Owner Stacks to enhance error overlays or other development tools. Owner Stacks are only available in development builds. Component Stacks in production are unchanged. + +* An Owner Stack is a development-only stack trace that helps identify which components are responsible for rendering a particular component. An Owner Stack is distinct from a Component Stacks, which shows the hierarchy of components leading to an error. +* The [captureOwnerStack API](https://react.dev/reference/react/captureOwnerStack) is only available in development mode and returns a Owner Stack, if available. The API can be used to enhance error overlays or log component relationships when debugging. [#29923](https://github.com/facebook/react/pull/29923), [#32353](https://github.com/facebook/react/pull/32353), [#30306](https://github.com/facebook/react/pull/30306), +[#32538](https://github.com/facebook/react/pull/32538), [#32529](https://github.com/facebook/react/pull/32529), [#32538](https://github.com/facebook/react/pull/32538) + +### React +* Enhanced support for Suspense boundaries to be used anywhere, including the client, server, and during hydration. [#32069](https://github.com/facebook/react/pull/32069), [#32163](https://github.com/facebook/react/pull/32163), [#32224](https://github.com/facebook/react/pull/32224), [#32252](https://github.com/facebook/react/pull/32252) +* Reduced unnecessary client rendering through improved hydration scheduling [#31751](https://github.com/facebook/react/pull/31751) +* Increased priority of client rendered Suspense boundaries [#31776](https://github.com/facebook/react/pull/31776) +* Fixed frozen fallback states by rendering unfinished Suspense boundaries on the client. [#31620](https://github.com/facebook/react/pull/31620) +* Reduced garbage collection pressure by improving Suspense boundary retries. [#31667](https://github.com/facebook/react/pull/31667) +* Fixed erroneous “Waiting for Paint” log when the passive effect phase was not delayed [#31526](https://github.com/facebook/react/pull/31526) +* Fixed a regression causing key warnings for flattened positional children in development mode. [#32117](https://github.com/facebook/react/pull/32117) +* Updated `useId` to use valid CSS selectors, changing format from `:r123:` to `«r123»`. [#32001](https://github.com/facebook/react/pull/32001) +* Added a dev-only warning for null/undefined created in useEffect, useInsertionEffect, and useLayoutEffect. [#32355](https://github.com/facebook/react/pull/32355) +* Fixed a bug where dev-only methods were exported in production builds. React.act is no longer available in production builds. [#32200](https://github.com/facebook/react/pull/32200) +* Improved consistency across prod and dev to improve compatibility with Google Closure Compiler and bindings [#31808](https://github.com/facebook/react/pull/31808) +* Improve passive effect scheduling for consistent task yielding. [#31785](https://github.com/facebook/react/pull/31785) +* Fixed asserts in React Native when passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent rendering. [#32528](https://github.com/facebook/react/pull/32528) +* Fixed component name resolution for Portal [#32640](https://github.com/facebook/react/pull/32640) +* Added support for beforetoggle and toggle events on the dialog element. [#32479](https://github.com/facebook/react/pull/32479) + +### React DOM +* Fixed double warning when the `href` attribute is an empty string [#31783](https://github.com/facebook/react/pull/31783) + * Fixed an edge case where `getHoistableRoot()` didn’t work properly when the container was a Document [#32321](https://github.com/facebook/react/pull/32321) +* Removed support for using HTML comments (e.g. ``) as a DOM container. [#32250](https://github.com/facebook/react/pull/32250) +* Added support for ` - - -``` +* [Quick Start](https://react.dev/learn) +* [Tutorial](https://react.dev/learn/tutorial-tic-tac-toe) +* [Thinking in React](https://react.dev/learn/thinking-in-react) +* [Installation](https://react.dev/learn/installation) +* [Describing the UI](https://react.dev/learn/describing-the-ui) +* [Adding Interactivity](https://react.dev/learn/adding-interactivity) +* [Managing State](https://react.dev/learn/managing-state) +* [Advanced Guides](https://react.dev/learn/escape-hatches) +* [API Reference](https://react.dev/reference/react) +* [Where to Get Support](https://react.dev/community) +* [Contributing Guide](https://legacy.reactjs.org/docs/how-to-contribute.html) -We've also built a [starter kit](http://facebook.github.io/react/downloads/react-0.12.2.zip) which might be useful if this is your first time using React. It includes a webpage with an example of using React with live code. +You can improve it by sending pull requests to [this repository](https://github.com/reactjs/react.dev). -If you'd like to use [bower](http://bower.io), it's as easy as: +## Examples -```sh -bower install --save react -``` +We have several examples [on the website](https://react.dev/). Here is the first one to get you started: -## Contribute +```jsx +import { createRoot } from 'react-dom/client'; -The main purpose of this repository is to continue to evolve React core, making it faster and easier to use. If you're interested in helping with that, then keep reading. If you're not interested in helping right now that's ok too. :) Any feedback you have about using React would be greatly appreciated. +function HelloMessage({ name }) { + return
Hello {name}
; +} -### Building Your Copy of React +const root = createRoot(document.getElementById('container')); +root.render(); +``` -The process to build `react.js` is built entirely on top of node.js, using many libraries you may already be familiar with. +This example will render "Hello Taylor" into a container on the page. -#### Prerequisites +You'll notice that we used an HTML-like syntax; [we call it JSX](https://react.dev/learn#writing-markup-with-jsx). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML. -* You have `node` installed at v0.10.0+ (it might work at lower versions, we just haven't tested). -* You are familiar with `npm` and know whether or not you need to use `sudo` when installing packages globally. -* You are familiar with `git`. +## Contributing -#### Build +The main purpose of this repository is to continue evolving React core, making it faster and easier to use. Development of React happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving React. -Once you have the repository cloned, building a copy of `react.js` is really easy. +### [Code of Conduct](https://code.fb.com/codeofconduct) -```sh -# grunt-cli is needed by grunt; you might have this installed already -npm install -g grunt-cli -npm install -grunt build -``` +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.fb.com/codeofconduct) so that you can understand what actions will and will not be tolerated. -At this point, you should now have a `build/` directory populated with everything you need to use React. The examples should all work. +### [Contributing Guide](https://legacy.reactjs.org/docs/how-to-contribute.html) -### Grunt +Read our [contributing guide](https://legacy.reactjs.org/docs/how-to-contribute.html) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to React. -We use grunt to automate many tasks. Run `grunt -h` to see a mostly complete listing. The important ones to know: +### [Good First Issues](https://github.com/facebook/react/labels/good%20first%20issue) -```sh -# Build and run tests with PhantomJS -grunt test -# Build and run tests in your browser -grunt test --debug -# For speed, you can use fasttest and add --filter to only run one test -grunt fasttest --filter=ReactIdentity -# Lint the code with ESLint -grunt lint -# Wipe out build directory -grunt clean -``` +To help you get your feet wet and get you familiar with our contribution process, we have a list of [good first issues](https://github.com/facebook/react/labels/good%20first%20issue) that contain bugs that have a relatively limited scope. This is a great place to get started. ### License -React is [BSD licensed](./LICENSE). We also provide an additional [patent grant](./PATENTS). - -React documentation is [Creative Commons licensed](./LICENSE-docs). - -Examples provided in this repository and in the documentation are [separately licensed](./LICENSE-examples). - -### More… - -There's only so much we can cram in here. To read more about the community and guidelines for submitting pull requests, please read the [Contributing document](CONTRIBUTING.md). +React is [MIT licensed](./LICENSE). diff --git a/ReactVersions.js b/ReactVersions.js new file mode 100644 index 000000000000..dd8c78afb133 --- /dev/null +++ b/ReactVersions.js @@ -0,0 +1,64 @@ +'use strict'; + +// This module is the single source of truth for versioning packages that we +// publish to npm. +// +// Packages will not be published unless they are added here. +// +// The @latest channel uses the version as-is, e.g.: +// +// 19.3.0 +// +// The @canary channel appends additional information, with the scheme +// -