diff --git a/.eslintrc.js b/.eslintrc.js index 7fe08f4cdf36e..49846c1f5e9bc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -446,10 +446,7 @@ module.exports = { }, }, { - files: [ - 'scripts/eslint-rules/*.js', - 'packages/eslint-plugin-react-hooks/src/*.js', - ], + files: ['scripts/eslint-rules/*.js'], plugins: ['eslint-plugin'], rules: { 'eslint-plugin/prefer-object-rule': ERROR, @@ -517,6 +514,26 @@ module.exports = { __IS_INTERNAL_VERSION__: 'readonly', }, }, + { + 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: { @@ -597,6 +614,9 @@ module.exports = { KeyframeAnimationOptions: 'readonly', GetAnimationsOptions: 'readonly', Animatable: 'readonly', + ScrollTimeline: 'readonly', + EventListenerOptionsOrUseCapture: 'readonly', + FocusOptions: 'readonly', spyOnDev: 'readonly', spyOnDevAndProd: 'readonly', diff --git a/.github/ISSUE_TEMPLATE/19.md b/.github/ISSUE_TEMPLATE/19.md deleted file mode 100644 index 6fa2dd8d8d8fe..0000000000000 --- a/.github/ISSUE_TEMPLATE/19.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: "⚛React 19 beta issue" -about: Report a issue with React 19 beta. -title: '[React 19]' -labels: 'React 19' - ---- - - -## Summary - - diff --git a/.github/workflows/compiler_discord_notify.yml b/.github/workflows/compiler_discord_notify.yml index 1ab9c1b1aff5b..71aea56e8492f 100644 --- a/.github/workflows/compiler_discord_notify.yml +++ b/.github/workflows/compiler_discord_notify.yml @@ -2,24 +2,41 @@ name: (Compiler) Discord Notify on: pull_request_target: - types: [labeled] + types: [opened, ready_for_review] paths: - compiler/** - .github/workflows/compiler_**.yml +permissions: {} + jobs: + check_access: + runs-on: ubuntu-latest + outputs: + is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }} + steps: + - 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' && github.event.label.name == 'React Core Team' }} + 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@v6.0.0 + uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4 with: webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }} embed-author-name: ${{ github.event.pull_request.user.login }} diff --git a/.github/workflows/compiler_playground.yml b/.github/workflows/compiler_playground.yml index d3d2420ee21d8..34349f584ef26 100644 --- a/.github/workflows/compiler_playground.yml +++ b/.github/workflows/compiler_playground.yml @@ -8,6 +8,8 @@ on: - 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 @@ -36,10 +38,27 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} + 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: npx playwright install-deps + if: steps.cache_playwright_browsers.outputs.cache-hit == 'true' - run: CI=true yarn test - run: ls -R test-results if: '!cancelled()' @@ -49,3 +68,4 @@ jobs: 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 index 4f4954dd952b4..2bb2c6ef16863 100644 --- a/.github/workflows/compiler_prereleases.yml +++ b/.github/workflows/compiler_prereleases.yml @@ -20,11 +20,12 @@ on: 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 - GH_TOKEN: ${{ github.token }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} defaults: @@ -46,9 +47,11 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} + 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' - name: Publish packages to npm run: | cp ./scripts/release/ci-npmrc ~/.npmrc diff --git a/.github/workflows/compiler_prereleases_manual.yml b/.github/workflows/compiler_prereleases_manual.yml index 3e42ae2cf200d..4960489590a4f 100644 --- a/.github/workflows/compiler_prereleases_manual.yml +++ b/.github/workflows/compiler_prereleases_manual.yml @@ -15,6 +15,8 @@ on: required: true type: string +permissions: {} + env: TZ: /usr/share/zoneinfo/America/Los_Angeles diff --git a/.github/workflows/compiler_prereleases_nightly.yml b/.github/workflows/compiler_prereleases_nightly.yml index 82f893aa5ef43..07919d7843b25 100644 --- a/.github/workflows/compiler_prereleases_nightly.yml +++ b/.github/workflows/compiler_prereleases_nightly.yml @@ -5,6 +5,8 @@ on: # 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 diff --git a/.github/workflows/compiler_prereleases_weekly.yml b/.github/workflows/compiler_prereleases_weekly.yml index 79a9451b6972a..72af00d52117c 100644 --- a/.github/workflows/compiler_prereleases_weekly.yml +++ b/.github/workflows/compiler_prereleases_weekly.yml @@ -5,6 +5,8 @@ on: # At 10 minutes past 9:00 on Mon - cron: 10 9 * * 1 +permissions: {} + env: TZ: /usr/share/zoneinfo/America/Los_Angeles diff --git a/.github/workflows/compiler_typescript.yml b/.github/workflows/compiler_typescript.yml index 95f8f1f26e112..6a3b52e21ae57 100644 --- a/.github/workflows/compiler_typescript.yml +++ b/.github/workflows/compiler_typescript.yml @@ -8,6 +8,8 @@ on: - 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 @@ -45,10 +47,13 @@ jobs: 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-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} + 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 @@ -66,17 +71,19 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} + 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 - continue-on-error: true strategy: + fail-fast: false matrix: workspace_name: ${{ fromJSON(needs.discover_yarn_workspaces.outputs.matrix) }} steps: @@ -90,7 +97,12 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} + 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_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml index 64d6707aa3688..0b70cfaf4e9ff 100644 --- a/.github/workflows/devtools_regression_tests.yml +++ b/.github/workflows/devtools_regression_tests.yml @@ -9,6 +9,8 @@ on: 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 @@ -18,6 +20,9 @@ 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 @@ -29,13 +34,15 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + 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 - - run: yarn install --frozen-lockfile - working-directory: scripts/release + 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 @@ -47,6 +54,7 @@ jobs: with: name: build path: build + if-no-files-found: error build_devtools_and_process_artifacts: name: Build DevTools and process artifacts @@ -63,11 +71,13 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + 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: @@ -83,23 +93,27 @@ jobs: with: name: react-devtools path: build/devtools.tgz + 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" @@ -108,7 +122,6 @@ jobs: - "17.0" - "18.0" - "18.2" # compiler polyfill - continue-on-error: true steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -120,9 +133,11 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + 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 @@ -135,6 +150,7 @@ jobs: needs: build_devtools_and_process_artifacts runs-on: ubuntu-latest strategy: + fail-fast: false matrix: version: - "16.0" @@ -142,7 +158,6 @@ jobs: - "16.8" # hooks - "17.0" - "18.0" - continue-on-error: true steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -154,17 +169,28 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + 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: Playwright install deps - run: | - npx playwright install - sudo npx playwright install-deps + - 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 }} @@ -176,3 +202,4 @@ jobs: with: name: screenshots path: ./tmp/screenshots + if-no-files-found: warn diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index 9a592780b17c4..1d0a896984e26 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -7,6 +7,8 @@ on: paths-ignore: - compiler/** +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: true @@ -17,6 +19,95 @@ env: 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.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-v6-${{ 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-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- + - 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-v6-${{ 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.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-v6-${{ 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-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }} + restore-keys: | + runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-and-compiler-node_modules-v6- + - 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-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }} + # ----- FLOW ----- discover_flow_inline_configs: name: Discover flow inline configs @@ -36,10 +127,10 @@ jobs: flow: name: Flow check ${{ matrix.flow_inline_config_shortname }} - needs: discover_flow_inline_configs + needs: [discover_flow_inline_configs, runtime_node_modules_cache] runs-on: ubuntu-latest - continue-on-error: true strategy: + fail-fast: false matrix: flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }} steps: @@ -52,19 +143,25 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- - 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 @@ -76,14 +173,19 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- - 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 --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false) @@ -91,6 +193,7 @@ jobs: # ----- FEATURE FLAGS ----- flags: name: Check flags + needs: [runtime_node_modules_cache] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -102,21 +205,25 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + 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' - 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" @@ -144,7 +251,6 @@ jobs: - 3/5 - 4/5 - 5/5 - continue-on-error: true steps: - uses: actions/checkout@v4 with: @@ -153,26 +259,37 @@ jobs: with: node-version-file: '.nvmrc' cache: yarn - cache-dependency-path: yarn.lock + cache-dependency-path: | + yarn.lock + compiler/yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }} + restore-keys: | + runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-and-compiler-node_modules-v6- - 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 }} # ----- 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] + 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 @@ -182,21 +299,30 @@ jobs: with: node-version-file: '.nvmrc' cache: yarn - cache-dependency-path: yarn.lock + 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@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }} + restore-keys: | + runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-and-compiler-node_modules-v6- - name: Ensure clean build directory run: rm -rf build - run: yarn install --frozen-lockfile - - run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci + 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 }} @@ -210,11 +336,13 @@ jobs: 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 + 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 @@ -247,10 +375,16 @@ jobs: # TODO: Test more persistent configurations? ] shard: - - 1/3 - - 2/3 - - 3/3 - continue-on-error: true + - 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 @@ -260,16 +394,25 @@ jobs: with: node-version-file: '.nvmrc' cache: yarn - cache-dependency-path: yarn.lock + cache-dependency-path: | + yarn.lock + compiler/yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }} + restore-keys: | + runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-and-compiler-node_modules-v6- - 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: @@ -282,7 +425,11 @@ jobs: process_artifacts_combined: name: Process artifacts combined - needs: build_and_lint + 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 @@ -294,14 +441,19 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- - 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: @@ -310,7 +462,7 @@ jobs: merge-multiple: true - name: Display structure of build run: ls -R build - - run: echo ${{ github.sha }} >> build/COMMIT_SHA + - run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA - name: Scrape warning messages run: | mkdir -p ./build/__test_utils__ @@ -320,16 +472,29 @@ jobs: # 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 + needs: [build_and_lint, runtime_node_modules_cache] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -341,14 +506,19 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- - 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: @@ -364,7 +534,7 @@ jobs: check_release_dependencies: name: Check release dependencies - needs: build_and_lint + needs: [build_and_lint, runtime_node_modules_cache] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -376,14 +546,19 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- - 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: @@ -408,15 +583,16 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + 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-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + 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 install --frozen-lockfile - working-directory: fixtures/dom + - 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: @@ -451,14 +627,31 @@ jobs: # 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 + 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-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + 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: @@ -467,16 +660,6 @@ jobs: merge-multiple: true - name: Display structure of build run: ls -R build - - name: Install fixture dependencies - working-directory: fixtures/flight - run: | - yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - if [ $? -ne 0 ]; then - yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - fi - - name: Playwright install deps - working-directory: fixtures/flight - run: npx playwright install --with-deps chromium - name: Run tests working-directory: fixtures/flight run: yarn test @@ -488,18 +671,21 @@ jobs: 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 + needs: [build_and_lint, runtime_node_modules_cache] runs-on: ubuntu-latest strategy: + fail-fast: false matrix: browser: [chrome, firefox, edge] steps: @@ -512,14 +698,19 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- - 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: @@ -537,6 +728,7 @@ jobs: with: name: react-devtools-${{ matrix.browser }}-extension path: build/devtools/${{ matrix.browser }}-extension.zip + if-no-files-found: error merge_devtools_artifacts: name: Merge DevTools artifacts @@ -551,7 +743,7 @@ jobs: run_devtools_e2e_tests: name: Run DevTools e2e tests - needs: build_and_lint + needs: [build_and_lint, runtime_node_modules_cache] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -563,14 +755,19 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + path: | + **/node_modules + key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}- + runtime-node_modules-v6- - 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: @@ -585,9 +782,13 @@ jobs: RELEASE_CHANNEL: experimental # ----- SIZEBOT ----- - download_base_build_for_sizebot: - if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' }} - name: Download base build for 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 @@ -599,57 +800,36 @@ jobs: cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules - uses: actions/cache@v4 + 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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + 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 - - run: yarn install --frozen-lockfile - working-directory: scripts/release + 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: | - git fetch origin main - GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse origin/main) + 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 - # 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. - 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 + - name: Display structure of base-build from origin/main run: ls -R base-build - - name: Archive base-build - uses: actions/upload-artifact@v4 - with: - name: base-build - path: base-build - - sizebot: - name: Run sizebot - needs: [build_and_lint, download_base_build_for_sizebot] - runs-on: ubuntu-latest - 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 - - name: Restore cached node_modules - uses: actions/cache@v4 - id: node_modules - with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} - name: Ensure clean build directory run: rm -rf build - - run: yarn install --frozen-lockfile - name: Restore archived build for PR uses: actions/download-artifact@v4 with: @@ -662,17 +842,11 @@ jobs: node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js - name: Display structure of build for PR run: ls -R build - - name: Restore archived base-build from origin/main - uses: actions/download-artifact@v4 - with: - name: base-build - path: base-build - - name: Display structure of base-build from origin/main - run: ls -R base-build - - run: echo ${{ github.sha }} >> build/COMMIT_SHA + - run: echo ${{ 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 index 4315256affe45..cc600d2085e50 100644 --- a/.github/workflows/runtime_commit_artifacts.yml +++ b/.github/workflows/runtime_commit_artifacts.yml @@ -16,6 +16,13 @@ on: 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 @@ -25,6 +32,40 @@ env: 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 }} @@ -64,27 +105,11 @@ jobs: 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" - - 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 + - name: Restore downloaded build + uses: actions/download-artifact@v4 with: - path: "**/node_modules" - key: runtime-release-node_modules-${{ 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 - name: yarn install (react) - - run: yarn install --frozen-lockfile - name: yarn install (scripts/release) - working-directory: scripts/release - - 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: build + path: build - name: Display structure of build run: ls -R build - name: Strip @license from eslint plugin and react-refresh @@ -107,9 +132,10 @@ jobs: mkdir ./compiled/facebook-www/__test_utils__ mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js - # Move eslint-plugin-react-hooks into facebook-www + # Move eslint-plugin-react-hooks into eslint-plugin-react-hooks + mkdir ./compiled/eslint-plugin-react-hooks mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \ - ./compiled/facebook-www/eslint-plugin-react-hooks.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 \ @@ -147,9 +173,9 @@ jobs: ls -R ./compiled-rn - name: Add REVISION files run: | - echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION + 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 ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION + 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: | @@ -166,15 +192,20 @@ jobs: 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 - if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0') + 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: @@ -186,12 +217,12 @@ jobs: name: compiled path: compiled/ - name: Revert version changes - if: needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '' + if: needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != '' env: - CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }} - CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }} - LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }} - LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }} + 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" @@ -221,12 +252,12 @@ jobs: 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.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '') + 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.download_artifacts.outputs.current_version_classic }} - CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }} - LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }} - LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }} + 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" @@ -240,24 +271,31 @@ jobs: - name: Will commit these changes if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true' run: | - echo ":" - git status -u + 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' - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: | - ${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }} + run: | + git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}" + git config --global user.name "${{ github.triggering_actor }}" - DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }}) - branch: builds/facebook-www - commit_user_name: ${{ github.triggering_actor }} - commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }} - create_branch: true + 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 - if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0') + 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 @@ -270,10 +308,10 @@ jobs: name: compiled-rn path: compiled-rn/ - name: Revert version changes - if: needs.download_artifacts.outputs.last_version_rn != '' + if: needs.process_artifacts.outputs.last_version_rn != '' env: - CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }} - LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }} + 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" @@ -299,10 +337,10 @@ jobs: 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.download_artifacts.outputs.last_version_rn != '') + if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_rn != '') env: - CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }} - LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }} + 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" @@ -409,15 +447,19 @@ jobs: 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' - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: | - ${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }} + run: | + git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}" + git config --global user.name "${{ github.triggering_actor }}" - DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }}) - branch: builds/facebook-fbsource - commit_user_name: ${{ github.triggering_actor }} - commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }} - create_branch: true + 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 index 59a1078b499e8..44775fbe78880 100644 --- a/.github/workflows/runtime_discord_notify.yml +++ b/.github/workflows/runtime_discord_notify.yml @@ -2,24 +2,41 @@ name: (Runtime) Discord Notify on: pull_request_target: - types: [labeled] + types: [opened, ready_for_review] paths-ignore: - compiler/** - .github/workflows/compiler_**.yml +permissions: {} + jobs: + check_access: + runs-on: ubuntu-latest + outputs: + is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }} + steps: + - 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' && github.event.label.name == 'React Core Team' }} + 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@v6.0.0 + uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4 with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} embed-author-name: ${{ github.event.pull_request.user.login }} diff --git a/.github/workflows/runtime_eslint_plugin_e2e.yml b/.github/workflows/runtime_eslint_plugin_e2e.yml new file mode 100644 index 0000000000000..92921646c1bca --- /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 index 66ddba318fa0a..a88ce523a62f6 100644 --- a/.github/workflows/runtime_fuzz_tests.yml +++ b/.github/workflows/runtime_fuzz_tests.yml @@ -8,6 +8,8 @@ on: - main workflow_dispatch: +permissions: {} + env: TZ: /usr/share/zoneinfo/America/Los_Angeles diff --git a/.github/workflows/runtime_prereleases.yml b/.github/workflows/runtime_prereleases.yml index e52bed6bb16bf..ee8dd72ce9665 100644 --- a/.github/workflows/runtime_prereleases.yml +++ b/.github/workflows/runtime_prereleases.yml @@ -13,21 +13,34 @@ on: 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 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 - GH_TOKEN: ${{ github.token }} 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 @@ -39,14 +52,24 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" - key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + 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 - - run: yarn install --frozen-lockfile - working-directory: scripts/release + 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: | - scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }} + GH_TOKEN=${{ secrets.GH_TOKEN }} scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }} cp ./scripts/release/ci-npmrc ~/.npmrc scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }} + - 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: 'Publish of $${{ inputs.release_channel }} 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 index 4c25ddc79bd35..71e25ba073a83 100644 --- a/.github/workflows/runtime_prereleases_manual.yml +++ b/.github/workflows/runtime_prereleases_manual.yml @@ -6,6 +6,8 @@ on: prerelease_commit_sha: required: true +permissions: {} + env: TZ: /usr/share/zoneinfo/America/Los_Angeles @@ -14,6 +16,9 @@ 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: ${{ inputs.prerelease_commit_sha }} release_channel: stable @@ -30,10 +35,14 @@ jobs: dist_tag: canary,next 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 @@ -45,3 +54,4 @@ jobs: dist_tag: experimental 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 index fe038042f332c..a38e241d53996 100644 --- a/.github/workflows/runtime_prereleases_nightly.yml +++ b/.github/workflows/runtime_prereleases_nightly.yml @@ -5,6 +5,8 @@ on: # 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 @@ -12,16 +14,25 @@ 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 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 @@ -31,5 +42,8 @@ jobs: commit_sha: ${{ github.sha }} release_channel: experimental dist_tag: experimental + enableFailureNotification: true 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 0000000000000..51e38439553de --- /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 index c20047d600321..f6eb9b9a6d122 100644 --- a/.github/workflows/shared_check_maintainer.yml +++ b/.github/workflows/shared_check_maintainer.yml @@ -6,14 +6,12 @@ on: actor: required: true type: string - is_remote: - required: false - type: boolean - default: false 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 @@ -22,10 +20,12 @@ env: 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: - - uses: actions/checkout@v4 - name: Check if actor is maintainer id: check_if_actor_is_maintainer uses: actions/github-script@v7 @@ -33,33 +33,20 @@ jobs: script: | const fs = require('fs'); const actor = '${{ inputs.actor }}'; - let isRemote = ${{ inputs.is_remote }}; - if (typeof isRemote === 'string') { - isRemote = isRemote === 'true'; - } - if (typeof isRemote !== 'boolean') { - throw new Error(`Invalid \`isRemote\` input. Expected a boolean, got: ${isRemote}`); - } - - let content = null; - if (isRemote === true) { - 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(); - } else { - content = await fs.readFileSync('./MAINTAINERS', { encoding: 'utf8' }); + 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'); } - if (content === null) { - throw new Error('Unable to retrieve local or http 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')); 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 0000000000000..ed80a505e4281 --- /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 0000000000000..f6c532b485a4d --- /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 0000000000000..01db0907401c0 --- /dev/null +++ b/.github/workflows/shared_close_direct_sync_branch_prs.yml @@ -0,0 +1,42 @@ +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 + 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 index dc432b54f72a6..fd4aa9399e386 100644 --- a/.github/workflows/shared_label_core_team_prs.yml +++ b/.github/workflows/shared_label_core_team_prs.yml @@ -2,6 +2,9 @@ name: (Shared) Label Core Team PRs on: pull_request_target: + types: [opened] + +permissions: {} env: TZ: /usr/share/zoneinfo/America/Los_Angeles @@ -9,8 +12,23 @@ env: 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: + - 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 }} @@ -18,6 +36,11 @@ jobs: 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 diff --git a/.github/workflows/shared_lint.yml b/.github/workflows/shared_lint.yml index 4b077eff65ab5..3c359cff2280c 100644 --- a/.github/workflows/shared_lint.yml +++ b/.github/workflows/shared_lint.yml @@ -5,6 +5,8 @@ on: branches: [main] pull_request: +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: true @@ -27,12 +29,15 @@ jobs: 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-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + 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: @@ -47,12 +52,15 @@ jobs: 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-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + 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: @@ -67,12 +75,15 @@ jobs: 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-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + 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: @@ -87,10 +98,13 @@ jobs: 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-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + 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 index 8d505e856e940..a2c707973c927 100644 --- a/.github/workflows/shared_stale.yml +++ b/.github/workflows/shared_stale.yml @@ -6,6 +6,8 @@ on: - cron: '0 * * * *' workflow_dispatch: +permissions: {} + env: TZ: /usr/share/zoneinfo/America/Los_Angeles diff --git a/.gitignore b/.gitignore index 2a20fc2427c98..6432df4f054d8 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ packages/react-devtools-fusebox/dist packages/react-devtools-inline/dist packages/react-devtools-shell/dist packages/react-devtools-timeline/dist + diff --git a/.nvmrc b/.nvmrc index ef33d6510196d..5f53e875de687 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.20.1 +v20.19.0 diff --git a/CHANGELOG-canary.md b/CHANGELOG-canary.md deleted file mode 100644 index 048f6a542e642..0000000000000 --- a/CHANGELOG-canary.md +++ /dev/null @@ -1,18 +0,0 @@ -## March 22, 2024 (18.3.0-canary-670811593-20240322) - -## React -- Added `useActionState` to replace `useFormState` and added `pending` value ([#28491](https://github.com/facebook/react/pull/28491)). - -## October 5, 2023 (18.3.0-canary-546178f91-20231005) - -### React - -- Added support for async functions to be passed to `startTransition`. -- `useTransition` now triggers the nearest error boundary instead of a global error. -- Added `useOptimistic`, a new Hook for handling optimistic UI updates. It optimistically updates the UI before receiving confirmation from a server or external source. - -### React DOM - -- Added support for passing async functions to the `action` prop on `