Skip to content

Sync bindings with upstream blend #88

Sync bindings with upstream blend

Sync bindings with upstream blend #88

Workflow file for this run

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