Repository navigation
ci: skip native E2E for a version-only release commit #694
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| name: Detect a version-only push | ||
| description: >- | ||
| Outputs native=false for a push whose single new commit only bumps the | ||
| package.json / oh-package.json5 version and edits .github/releases/** | ||
| (scripts/version-only-commit.js), so the native jobs can skip a release | ||
| commit that builds exactly what its already-tested parent built. The | ||
| decision is recorded as the "native-inherited" commit status, which makes | ||
| publish.yml require the parent's native-required as well. Everything else, | ||
| pull requests included, gets native=true. Needs a checkout with | ||
| fetch-depth >= 2 and `statuses: write`. | ||
| outputs: | ||
| native: | ||
| description: "'false' to skip the native jobs, otherwise 'true'." | ||
| value: ${{ steps.check.outputs.native }} | ||
| runs: | ||
| using: composite | ||
| steps: | ||
| - id: check | ||
| shell: bash | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| BEFORE: ${{ github.event.before }} | ||
| run: | | ||
| native=true | ||
| parent="$(git rev-parse --verify --quiet HEAD^1 || true)" | ||
| # Only when the push added exactly this one commit: a push that | ||
| # brought several gets no runs for the earlier ones, so the parent | ||
| # would have no native-required to rely on. | ||
| if [ "$EVENT_NAME" = push ] && [ -n "$parent" ] && [ "$parent" = "$BEFORE" ] \ | ||
| && [ "$(node scripts/version-only-commit.js HEAD)" = true ]; then | ||
| native=false | ||
| echo "::notice::Version-only commit: native jobs are skipped; publishing relies on the native-required of $parent." | ||
| gh api "repos/$GITHUB_REPOSITORY/statuses/$(git rev-parse HEAD)" \ | ||
| -f state=success -f context=native-inherited \ | ||
| -f description="native E2E inherited from ${parent:0:12}" \ | ||
| -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$parent" >/dev/null | ||
| fi | ||
| echo "native=$native" >> "$GITHUB_OUTPUT" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,12 @@ jobs: | |
| harmony_changed: ${{ steps.check.outputs.changed }} | ||
| prev_tag: ${{ steps.check.outputs.prev_tag }} | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| fetch-depth: 0 | ||
| # Only tags, commits and trees are read (git tag --merged, git diff | ||
| # --name-only, git rev-parse); skip the blobs of the whole history. | ||
| filter: blob:none | ||
| # Both publish jobs need this job, so a release can only ship a commit | ||
| # whose lint/test/native E2E gate (native-gate.yml posts it as the | ||
| # "native-required" commit status) is green. Pending or not yet posted | ||
|
|
@@ -31,31 +37,46 @@ jobs: | |
| GH_TOKEN: ${{ github.token }} | ||
| SHA: ${{ github.sha }} | ||
| run: | | ||
| # A version-only release commit skips its native jobs on push | ||
| # (.github/actions/version-only posts "native-inherited" for it, | ||
| # before any of its workflows completes), so its own | ||
| # native-required only covers lint and test; the native E2E it | ||
| # ships ran on its parent, whose native-required must be green too. | ||
| status() { | ||
| gh api "repos/$GITHUB_REPOSITORY/commits/$1/status" \ | ||
| --jq "[.statuses[] | select(.context == \"$2\")][0].state // \"missing\"" | ||
| } | ||
| # The gate turns green only after the slowest native E2E: e2e-ios | ||
| # is a build job (timeout 45 min) followed by its test legs | ||
| # (timeout 60 min), plus macOS queueing. A green release commit | ||
| # took 55 min (10.60.0), so 45 failed releases cut right after the | ||
| # push; wait out both budgets instead. | ||
| # Checks at minute 0 through 120: the last poll lands on the | ||
| # boundary itself, with no trailing sleep before giving up. | ||
| required="$SHA" | ||
| for attempt in $(seq 0 120); do | ||
| state="$(gh api "repos/$GITHUB_REPOSITORY/commits/$SHA/status" \ | ||
| --jq '[.statuses[] | select(.context == "native-required")][0].state // "missing"')" | ||
| echo "native-required for $SHA: $state" | ||
| case "$state" in | ||
| success) exit 0 ;; | ||
| pending|missing) if [ "$attempt" -lt 120 ]; then sleep 60; fi ;; | ||
| *) echo "::error::native-required is $state for $SHA; fix CI before publishing"; exit 1 ;; | ||
| esac | ||
| green=true | ||
| for sha in $required; do | ||
| state="$(status "$sha" native-required)" | ||
| echo "native-required for $sha: $state" | ||
| case "$state" in | ||
| success) ;; | ||
| pending|missing) green=false ;; | ||
| *) echo "::error::native-required is $state for $sha; fix CI before publishing"; exit 1 ;; | ||
| esac | ||
| done | ||
| if [ "$green" = true ]; then | ||
| if [ "$required" = "$SHA" ] && [ "$(status "$SHA" native-inherited)" = success ]; then | ||
| required="$SHA $(git rev-parse "$SHA^1")" | ||
| echo "$SHA only bumps the version; also requiring its parent's native-required." | ||
| continue | ||
| fi | ||
| exit 0 | ||
| fi | ||
| if [ "$attempt" -lt 120 ]; then sleep 60; fi | ||
|
Comment on lines
+56
to
+76
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,90p' .github/workflows/publish.yml
cat .github/workflows/native-gate.yml
cat .github/actions/version-only/action.ymlRepository: reactnativecn/react-native-update Length of output: 9170 🏁 Script executed: set -o pipefail
printf '%s\n' '--- version-only callers ---'
rg -n -F --glob '.github/**' -e '.github/actions/version-only' -e 'version-only' -e 'chore: release' -e 'release --' -e 'npm version' -e 'git commit' .github scripts package.json docs README.md 2>/dev/null || true
printf '%s\n' '--- workflow files ---'
rg --files .github/workflows .github/actions | sort
printf '%s\n' '--- relevant workflow excerpts ---'
for f in .github/workflows/e2e-android.yml .github/workflows/e2e-ios.yml .github/workflows/harmony-build.yml .github/workflows/expo-build.yml .github/workflows/lint.yml .github/workflows/test.yml; do
if [ -f "$f" ]; then
echo "### $f"
nl -ba "$f" | sed -n '1,240p' "$f"
fi
done
printf '%s\n' '--- version script and tests ---'
nl -ba scripts/version-only-commit.js | sed -n '1,240p'
if [ -f src/__tests__/versionOnlyCommit.test.ts ]; then
nl -ba src/__tests__/versionOnlyCommit.test.ts | sed -n '1,260p'
fi
printf '%s\n' '--- release config files ---'
find . -maxdepth 3 -type f \( -iname '*release*' -o -iname '*publish*' \) -print | sort
printf '%s\n' '--- diff base/head paths and relevant diff ---'
git diff --name-status c98c2d630682c3eb96e3226acfd6cec4d6dd780d 07348f21e935d84bdd5f4b68f8088b914cb898fa -- .github scripts package.json
git diff --unified=35 c98c2d630682c3eb96e3226acfd6cec4d6dd780d 07348f21e935d84bdd5f4b68f8088b914cb898fa -- .github/workflows/publish.yml .github/workflows/native-gate.yml .github/actions/version-only/action.yml scripts/version-only-commit.jsRepository: reactnativecn/react-native-update Length of output: 42666 🏁 Script executed: set -o pipefail
for f in .github/actions/version-only/action.yml .github/workflows/native-gate.yml .github/workflows/publish.yml .github/workflows/lint.yml .github/workflows/test.yml .github/workflows/e2e_android.yml .github/workflows/e2e_ios.yml .github/workflows/expo_build.yml .github/workflows/harmony-build.yml; do
echo "### $f"
nl -ba "$f" | sed -n '1,125p'
done
printf '%s\n' '--- release-related workflow triggers and history ---'
rg -n -F --glob '.github/workflows/**' -e 'release:' -e 'types: [published]' -e 'push:' -e 'branches:' -e 'native-inherited' -e 'native-required' .github/workflows
git log --oneline --decorate -12 -- .github/actions/version-only .github/workflows/publish.yml .github/workflows/native-gate.yml scripts/version-only-commit.jsRepository: reactnativecn/react-native-update Length of output: 41805 Prevent consecutive version-only inheritance. A version-only commit can follow another version-only commit. The action does not check whether the parent already has Suggested fix if [ "$EVENT_NAME" = push ] && [ -n "$parent" ] && [ "$parent" = "$BEFORE" ] \
+ && [ "$(gh api "repos/$GITHUB_REPOSITORY/commits/$parent/status" \
+ --jq '[.statuses[] | select(.context == "native-inherited")][0].state // "missing"')" != success ] \
&& [ "$(node scripts/version-only-commit.js HEAD)" = true ]; then🤖 Prompt for AI Agents |
||
| done | ||
| echo "::error::native-required for $SHA did not turn green within 120 minutes" | ||
| echo "::error::native-required for $required did not turn green within 120 minutes" | ||
| exit 1 | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| fetch-depth: 0 | ||
| # Only tags, commits and trees are read (git tag --merged, git diff | ||
| # --name-only); skip the blobs of the whole history. | ||
| filter: blob:none | ||
| - name: Check Harmony changes | ||
| id: check | ||
| shell: bash | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: reactnativecn/react-native-update
Length of output: 13015
🏁 Script executed:
Repository: reactnativecn/react-native-update
Length of output: 37913
Align inheritance with the release concurrency rule.
A non-
chore: releaseversion-only push can enter this action, whilee2e-android,e2e-ios,expo-build, andharmony-builduse the shared ref concurrency group andcancel-in-progress: true. The new run can cancel the parent’s native checks.native-gatecan then publish a non-successnative-requiredstatus for the parent.publish.ymldoes not run for the push itself. It runs only forrelease.published. If a release is later published for this commit, its gate detectsnative-inherited, requires the parent’snative-required, and can fail before publishing. If no release is published, there is no publish effect.Require the same release-message prefix before classifying the commit as inherited:
Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents