Sync bindings with upstream blend #152
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync bindings with upstream blend | |
| # Two ways to run: | |
| # 1. schedule — two daily polls of @juspay/blend-design-system: | |
| # 03:00 UTC -> STABLE channel (blend `latest` dist-tag) | |
| # 15:00 UTC -> BETA channel (blend `beta` dist-tag) | |
| # If the channel's dist-tag has advanced, regenerates bindings and opens a PR. | |
| # 2. workflow_dispatch — a maintainer regenerates a specific version on demand | |
| # (allow_prerelease=true selects the beta channel). | |
| # Generation is deterministic (@juspay/rescript-bindgen) — no LLM, no secrets. | |
| # Prereleases are accepted only on the beta channel; the stable channel refuses them. | |
| on: | |
| schedule: | |
| # Stable channel — daily at 03:00 UTC. Picks up a new stable blend within ~24h. | |
| - cron: '0 3 * * *' | |
| # Beta channel — daily at 15:00 UTC. Picks up a new blend beta within ~24h. | |
| - cron: '0 15 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| blend_version: | |
| description: 'Version spec to install (e.g. "latest", "1.2.3", or a pkg.pr.new URL for local testing only)' | |
| required: false | |
| default: 'latest' | |
| type: string | |
| allow_prerelease: | |
| description: 'Allow -beta / -alpha / -rc versions. Required to cut a beta release; the merged PR publishes under the npm `beta` dist-tag (never `latest`).' | |
| required: false | |
| default: false | |
| type: boolean | |
| concurrency: | |
| group: sync-bindings | |
| cancel-in-progress: false | |
| jobs: | |
| sync: | |
| name: Regenerate bindings | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| # Determine the release channel: | |
| # - scheduled 03:00 UTC cron -> stable; scheduled 15:00 UTC cron -> beta | |
| # - manual run -> beta when allow_prerelease=true, else stable | |
| - name: Determine channel | |
| id: chan | |
| run: | | |
| MODE=stable | |
| if [ "${{ github.event_name }}" = "schedule" ]; then | |
| [ "${{ github.event.schedule }}" = "0 15 * * *" ] && MODE=beta | |
| else | |
| [ "${{ inputs.allow_prerelease }}" = "true" ] && MODE=beta | |
| fi | |
| echo "mode=$MODE" >> $GITHUB_OUTPUT | |
| echo "channel = $MODE" | |
| # On the daily schedule, compare the channel's blend dist-tag on npm against the | |
| # matching tag we last published, and skip the rest of the job (no PR) unless it | |
| # changed. We only ever read the current dist-tag (never backfill skipped versions); | |
| # dist-tags are expected to move forward. Manual runs skip this gate. | |
| - name: Check for a newer blend (scheduled runs) | |
| id: poll | |
| if: github.event_name == 'schedule' | |
| run: | | |
| if [ "${{ steps.chan.outputs.mode }}" = "beta" ]; then | |
| # Last BETA we published vs blend's current beta dist-tag. | |
| CURRENT=$(npm view @juspay/rescript-blend dist-tags.beta 2>/dev/null || echo 0) | |
| LATEST=$(npm view @juspay/blend-design-system dist-tags.beta 2>/dev/null || echo 0) | |
| else | |
| # Last STABLE we published (our npm `latest` dist-tag), NOT the devDep pin, so | |
| # beta syncs that pin the devDep don't make this stable poll churn. | |
| CURRENT=$(npm view @juspay/rescript-blend version 2>/dev/null || echo 0) | |
| LATEST=$(npm view @juspay/blend-design-system version) # dist-tag 'latest' is stable-only | |
| fi | |
| echo "mode=${{ steps.chan.outputs.mode }} current=$CURRENT latest=$LATEST" | |
| if [ -z "$LATEST" ] || [ "$LATEST" = "0" ]; then | |
| echo "No ${{ steps.chan.outputs.mode }} version available upstream. Nothing to sync." | |
| echo "proceed=false" >> $GITHUB_OUTPUT | |
| elif [ "$CURRENT" = "$LATEST" ]; then | |
| echo "Already on the latest ${{ steps.chan.outputs.mode }} blend ($CURRENT). Nothing to sync." | |
| echo "proceed=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "proceed=true" >> $GITHUB_OUTPUT | |
| echo "spec=$LATEST" >> $GITHUB_OUTPUT | |
| fi | |
| # Resolve which version spec to install: the poll result on schedule, | |
| # else the manual input. | |
| - name: Resolve target spec | |
| id: target | |
| if: github.event_name != 'schedule' || steps.poll.outputs.proceed == 'true' | |
| run: | | |
| if [ "${{ github.event_name }}" = "schedule" ]; then | |
| echo "spec=${{ steps.poll.outputs.spec }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "spec=${{ inputs.blend_version }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Install target blend version | |
| if: steps.target.outputs.spec != '' | |
| run: npm install --no-save "@juspay/blend-design-system@${{ steps.target.outputs.spec }}" | |
| - name: Resolve installed blend version | |
| id: blend | |
| if: steps.target.outputs.spec != '' | |
| run: | | |
| V=$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('node_modules/@juspay/blend-design-system/package.json','utf8')).version)") | |
| echo "version=$V" >> $GITHUB_OUTPUT | |
| echo "Resolved blend version: $V" | |
| - name: Refuse prerelease versions (unless beta channel) | |
| if: steps.target.outputs.spec != '' | |
| run: | | |
| V="${{ steps.blend.outputs.version }}" | |
| if [[ "$V" =~ -(beta|alpha|rc) ]]; then | |
| if [[ "${{ steps.chan.outputs.mode }}" != "beta" ]]; then | |
| echo "Refusing to sync prerelease version $V on the stable channel." | |
| echo "The stable path only accepts stable releases." | |
| echo "Betas are handled by the beta channel (the 15:00 UTC cron, or a manual run with allow_prerelease=true);" | |
| echo "the merged PR publishes under the npm 'beta' dist-tag (never 'latest')." | |
| exit 1 | |
| else | |
| echo "::warning::Proceeding against prerelease $V (beta channel). On merge this publishes under the 'beta' dist-tag." | |
| fi | |
| fi | |
| # `npm run generate -- --set-version` is the SINGLE source of truth for the pin: it | |
| # writes package.json `version` + `dependencies["@juspay/blend-design-system"]` (EXACT, | |
| # 1:1 with the bindings) and refreshes package-lock.json. Blend ships as a runtime | |
| # `dependency` (NOT a peer) — for BOTH stable and beta — so consumers install only | |
| # @juspay/rescript-blend and receive Blend transitively; `npm ci` at any tag | |
| # re-generates byte-identical src (determinism). The stable poll's baseline is | |
| # decoupled (see "Check for a newer stable blend"), so a beta pin no longer churns. | |
| - name: Regenerate all bindings + pin blend (deterministic) | |
| if: steps.target.outputs.spec != '' | |
| run: npm run generate -- --blend "${{ steps.blend.outputs.version }}" --set-version | |
| - name: Build to confirm green | |
| if: steps.target.outputs.spec != '' | |
| run: npm run build | |
| - name: Open PR with regenerated bindings | |
| if: steps.target.outputs.spec != '' | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| branch: sync/blend-${{ steps.blend.outputs.version }} | |
| title: 'chore(sync): regenerate bindings for @juspay/blend-design-system@${{ steps.blend.outputs.version }}' | |
| commit-message: | | |
| chore(sync): regenerate bindings for @juspay/blend-design-system@${{ steps.blend.outputs.version }} | |
| body: | | |
| Regenerates ReScript bindings against `@juspay/blend-design-system@${{ steps.blend.outputs.version }}` | |
| using `@juspay/rescript-bindgen` (deterministic — same blend version always | |
| produces byte-identical output). `package.json` version is set to match (1:1). | |
| **Reviewer checklist** | |
| - [ ] `npm run build` is green (CI will block if not) | |
| - [ ] `src/_REPORT.md` shows 0 broken components; skim any flagged/loose props | |
| - [ ] Diff looks sensible — especially prop/variant renames that could break downstream consumers | |
| - [ ] Version = blend version (set automatically); use a `-N` suffix only for a binding-only re-release of an unchanged blend version | |
| _${{ github.event_name == 'schedule' && 'Opened automatically by the daily poll.' || format('Triggered manually by {0} (spec {1}).', github.actor, inputs.blend_version) }}_ | |
| labels: | | |
| automated | |
| sync | |
| add-paths: | | |
| src/** | |
| package.json | |
| package-lock.json |