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