From 5bc562c88b9c74e324cf72733d04850189a5cabf Mon Sep 17 00:00:00 2001 From: Gabriel Taveira Date: Tue, 19 May 2026 17:12:44 -0300 Subject: [PATCH 1/3] chore(ci): properly detect no-op releases and skip remote git push The previous probe ran `release-it --release-version --ci --no-npm --no-github` and grepped the output for a semver. When no commits match the `(modal)` scope filter, the conventional-changelog plugin returns no recommendation and release-it's Version plugin falls back to a CI patch of `latestVersion` (which is `0.0.0` here because no `magic-modal-*` tag exists yet). That printed `0.0.1`, which is not the current package version (`7.0.1`), so the skip check incorrectly proceeded to publish. Replace it with a deterministic git-history probe: pick the most recent of (last `magic-modal-*` tag, last `chore(release):` commit, last `chore(modal): sync version` commit) as the boundary, then grep `git log ..HEAD` for `(modal)`-scoped commits or `BREAKING CHANGE` footers. Zero matches => skip cleanly. Also flip `.release-it.js` `git.push` to `false`. The `GH_PAT` secret (2024-vintage) is being rejected by branch protection on push, which crashed the workflow AFTER `npm publish` had already succeeded and left npm and main desynced. Keep `commit: true` and `tag: true` so the `@release-it/github` plugin still has a local tag to attach the release to on the runner; sync the version bump back to main via a follow-up PR (same pattern as #192). `fetch-depth: 0` is added to checkout so the probe can see full history. --- .github/workflows/release.yml | 55 +++++++++++++++++++++++++++++------ packages/modal/.release-it.js | 17 ++++++++++- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 90f8462..fada972 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,8 +14,15 @@ jobs: - name: 🏗 Setup Repo uses: actions/checkout@v4 with: + # Need full history so the no-op probe below can locate the last + # release boundary commit and grep for qualifying commits since. + fetch-depth: 0 # PAT lets release-it push the version-bump commit + tag back to # main past branch protection (GITHUB_TOKEN can't bypass rules). + # NOTE: GH_PAT was failing with "Permission denied" on push (likely + # expired/missing contents:write). We keep this here in case it gets + # rotated, but `.release-it.js` now sets `git.push: false` so the + # workflow no longer depends on it actually working. token: ${{ secrets.GH_PAT }} persist-credentials: true @@ -64,20 +71,50 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # release-it prints the next version it WOULD publish. If it matches - # the version already on disk (== last published), there is nothing - # to ship. We disable npm/github side-effects on this probe so it - # only computes the version from conventional commits. + # We can't trust `release-it --release-version` here: when no commits + # match the `(modal)` scope filter, the conventional-changelog plugin + # returns no recommendation and release-it's Version plugin falls + # back to a CI-default `patch` of `latestVersion` (which is `0.0.0` + # because there's no `magic-modal-*` git tag yet). That made the + # probe print `0.0.1` even with zero qualifying commits, so the + # script proceeded to publish. + # + # Instead, decide skip deterministically from git history. The + # boundary is the most recent of: + # - last `magic-modal-*` tag (preferred, once we ever push one) + # - last `chore(release):` commit (release-it's own bump commit) + # - last `chore(modal): sync version` commit (manual sync fallback) + # We then look for any commit since the boundary whose subject is + # `(modal)`-scoped OR whose body has a `BREAKING CHANGE:` footer. + # This mirrors the commitFilter in packages/modal/.release-it.js. CURRENT=$(node -p "require('./package.json').version") - NEXT=$(pnpm exec release-it --release-version --ci --no-npm --no-github 2>&1 | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1 | tr -d '[:space:]') echo "current=$CURRENT" - echo "next=$NEXT" echo "current=$CURRENT" >> "$GITHUB_OUTPUT" - echo "next=$NEXT" >> "$GITHUB_OUTPUT" - if [ -z "$NEXT" ] || [ "$NEXT" = "$CURRENT" ]; then + + TAG_BOUND=$(git describe --tags --abbrev=0 --match='magic-modal-*' 2>/dev/null || true) + REL_BOUND=$(git log -1 --pretty=%H --grep='^chore(release)' 2>/dev/null || true) + SYNC_BOUND=$(git log -1 --pretty=%H --grep='^chore(modal): sync version' 2>/dev/null || true) + CANDIDATES=$(printf '%s\n%s\n%s\n' "$TAG_BOUND" "$REL_BOUND" "$SYNC_BOUND" | grep -v '^$' || true) + + if [ -z "$CANDIDATES" ]; then + # No boundary at all (fresh repo) — let the release flow run. + echo "No release boundary found; proceeding." + echo "skip=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + BOUNDARY=$(echo "$CANDIDATES" | xargs -I{} git log -1 --pretty="%ct %H" {} 2>/dev/null | sort -rn | head -1 | awk '{print $2}') + echo "boundary=$BOUNDARY ($(git log -1 --pretty=%s "$BOUNDARY"))" + + # `\(modal\)` matches scoped commits; `BREAKING[- ]CHANGE` matches + # the footer added by conventional-commits for breaking changes. + QUALIFY=$(git log "$BOUNDARY..HEAD" -E --grep='\(modal\)|BREAKING[- ]CHANGE' --pretty=%s || true) + if [ -z "$QUALIFY" ]; then + echo "No qualifying commits since boundary. Skipping release." echo "skip=true" >> "$GITHUB_OUTPUT" - echo "No new version to release. Skipping." else + echo "Qualifying commits found:" + echo "$QUALIFY" echo "skip=false" >> "$GITHUB_OUTPUT" fi diff --git a/packages/modal/.release-it.js b/packages/modal/.release-it.js index 76c80b3..4e63187 100644 --- a/packages/modal/.release-it.js +++ b/packages/modal/.release-it.js @@ -76,7 +76,22 @@ export default { pushArgs: ["-o ci.skip"], commit: true, tag: true, - push: true, + // We intentionally do NOT push the release commit/tag back to main from + // CI. The repository's GH_PAT secret (dated 2024) is currently rejected + // by branch protection ("Permission to GSTJ/react-native-magic-modal.git + // denied to GSTJ"), which causes the entire publish workflow to fail + // AFTER npm publish has already happened — leaving npm and main out of + // sync and bricking the workflow forever after. + // + // Keeping `commit: true` and `tag: true` so the @release-it/github plugin + // still has a tag to attach the GitHub Release to within the runner. + // The bump commit + tag exist only on the runner and are discarded when + // the job ends; main stays at the pre-release version, and we sync via + // a follow-up PR (same pattern used for #192). + // + // TODO: once GH_PAT is rotated with `contents: write` and granted + // bypass on branch protection, flip `push` back to `true`. + push: false, requireCleanWorkingDir: false, tagName: "magic-modal-${version}", }, From e198abcd8441a2c73f8aabd5fea2b9cd1b3179cc Mon Sep 17 00:00:00 2001 From: Gabriel Taveira Date: Tue, 19 May 2026 17:44:43 -0300 Subject: [PATCH 2/3] ci: trigger pr synchronize From eafdf760e19bd220a8cb9bf2ecc7fc21568ab2a6 Mon Sep 17 00:00:00 2001 From: Gabriel Taveira Date: Tue, 19 May 2026 17:45:13 -0300 Subject: [PATCH 3/3] ci(e2e): add opened/reopened to PR trigger types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without these, the E2E workflow doesn't fire when a PR is first opened — it only fires on subsequent commits (synchronize). That caused PR #195 to sit in BLOCKED without ever running the required check until an empty commit was pushed. --- .github/workflows/e2e-ios.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-ios.yml b/.github/workflows/e2e-ios.yml index f2f488a..280af12 100644 --- a/.github/workflows/e2e-ios.yml +++ b/.github/workflows/e2e-ios.yml @@ -5,7 +5,7 @@ on: pull_request: branches: - main - types: [synchronize, ready_for_review] + types: [opened, reopened, synchronize, ready_for_review] concurrency: # Key off the PR number (works across pull_request and pull_request_review