From 99cf371852eebb1d61123efc5c3e2f07632d517e Mon Sep 17 00:00:00 2001 From: Emily Brown Date: Wed, 4 Feb 2026 13:25:51 -0800 Subject: [PATCH] Add api-changes.yml workflow to replace danger-pr (#55341) Summary: Changelog: [INTERNAL] [CHANGED] - Add api-changes.yml workflow to replace danger-pr for detecting changes in the js api The workflow: - Triggers on `pull_request_target` for opened, edited, reopened, and synchronize events - Checks out the main branch (for security, using trusted code) - Runs the `diff-js-api-changes` action to detect API changes - Posts a PR comment using the generic `post-pr-comment` action The PR comment script creates, updates, or deletes a bot comment based on whether there are any sections to report, using a marker to identify existing bot comments. Reviewed By: huntie Differential Revision: D90991845 --- .../actions/diff-js-api-changes/action.yml | 36 ++++++++++++ .github/actions/post-pr-comment/action.yml | 55 +++++++++++++++++++ .github/workflows/api-changes.yml | 28 ++++++++++ 3 files changed, 119 insertions(+) create mode 100644 .github/actions/post-pr-comment/action.yml create mode 100644 .github/workflows/api-changes.yml diff --git a/.github/actions/diff-js-api-changes/action.yml b/.github/actions/diff-js-api-changes/action.yml index 54d7e51180a063..cd6a780505ea37 100644 --- a/.github/actions/diff-js-api-changes/action.yml +++ b/.github/actions/diff-js-api-changes/action.yml @@ -1,5 +1,9 @@ name: diff-js-api-changes description: Check for breaking changes in the public React Native JS API +outputs: + message: + description: Formatted markdown message describing API changes, or empty if no changes + value: ${{ steps.format_output.outputs.message }} runs: using: composite steps: @@ -35,3 +39,35 @@ runs: $SCRATCH_DIR/ReactNativeApi-before.d.ts \ $SCRATCH_DIR/ReactNativeApi-after.d.ts \ > $SCRATCH_DIR/output.json + + - name: Format output message + id: format_output + shell: bash + env: + SCRATCH_DIR: ${{ runner.temp }}/diff-js-api-changes + run: | + if [ ! -f "$SCRATCH_DIR/output.json" ]; then + echo "message=" >> $GITHUB_OUTPUT + exit 0 + fi + + RESULT=$(cat $SCRATCH_DIR/output.json | jq -r '.result // empty') + if [ -z "$RESULT" ] || [ "$RESULT" = "NON_BREAKING" ]; then + echo "message=" >> $GITHUB_OUTPUT + exit 0 + fi + + # Use delimiter for multiline output + { + echo "message< [!WARNING]" + echo "> **JavaScript API change detected**" + echo ">" + echo "> This PR commits an update to \`ReactNativeApi.d.ts\`, indicating a change to React Native's public JavaScript API." + echo ">" + echo "> - Please include a **clear changelog message**." + echo "> - This change will be subject to additional review." + echo ">" + echo "> This change was flagged as: \`${RESULT}\`" + echo "EOF" + } >> $GITHUB_OUTPUT diff --git a/.github/actions/post-pr-comment/action.yml b/.github/actions/post-pr-comment/action.yml new file mode 100644 index 00000000000000..e289df1c48a78e --- /dev/null +++ b/.github/actions/post-pr-comment/action.yml @@ -0,0 +1,55 @@ +name: post-pr-comment +description: Post or update a PR comment, or delete if no sections +inputs: + sections: + description: 'JSON array of markdown sections to include in comment' + required: false + default: '[]' + header: + description: 'Optional header text to display at the top of the comment' + required: false + default: '' + marker: + description: 'HTML comment marker to identify this comment' + required: true +runs: + using: composite + steps: + - name: Create, update, or delete comment + uses: actions/github-script@v8 + env: + SECTIONS_INPUT: ${{ inputs.sections }} + MARKER_INPUT: ${{ inputs.marker }} + HEADER_INPUT: ${{ inputs.header }} + with: + script: | + const marker = process.env.MARKER_INPUT; + const header = process.env.HEADER_INPUT; + const sections = JSON.parse(process.env.SECTIONS_INPUT) + .filter(Boolean) + .filter(s => s.trim()); + + const {owner, repo} = context.repo; + const issue_number = context.issue.number; + + const {data: comments} = await github.rest.issues.listComments({ + owner, repo, issue_number, + }); + + const existing = comments.find(c => c.body?.includes(marker)); + + if (!sections.length) { + if (existing) { + await github.rest.issues.deleteComment({owner, repo, comment_id: existing.id}); + } + return; + } + + const content = sections.join('\n\n'); + const body = header ? `${marker}\n## ${header}\n\n${content}` : `${marker}\n${content}`; + + if (existing) { + await github.rest.issues.updateComment({owner, repo, comment_id: existing.id, body}); + } else { + await github.rest.issues.createComment({owner, repo, issue_number, body}); + } diff --git a/.github/workflows/api-changes.yml b/.github/workflows/api-changes.yml new file mode 100644 index 00000000000000..29d49515840557 --- /dev/null +++ b/.github/workflows/api-changes.yml @@ -0,0 +1,28 @@ +name: Validate API snapshot changes + +on: + pull_request_target: + types: [opened, edited, reopened, synchronize] + +permissions: + pull-requests: write + +jobs: + api-changes: + runs-on: ubuntu-latest + if: github.repository == 'facebook/react-native' + steps: + - name: Check out main branch + uses: actions/checkout@v6 + - name: Setup Node.js + uses: ./.github/actions/setup-node + - name: Run yarn install + uses: ./.github/actions/yarn-install + - name: Run diff-js-api-changes + id: diff-js-api-changes + uses: ./.github/actions/diff-js-api-changes + - name: Post PR comment + uses: ./.github/actions/post-pr-comment + with: + marker: '' + sections: '[${{ toJSON(steps.diff-js-api-changes.outputs.message) }}]'