Release and Publish #51
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: Release and Publish | |
| # 1:1 versioning: @juspay/rescript-blend@X ships bindings for @juspay/blend-design-system@X. | |
| # The version lives in package.json (set by the sync PR). On push to main we publish | |
| # that version — but only if it isn't already on npm, so docs/chore commits don't | |
| # trigger a duplicate-publish failure. (Replaces semantic-release.) | |
| # | |
| # workflow_dispatch adds an on-demand BINDING-ONLY RE-RELEASE path for an OLD blend | |
| # line that can't go through main (main tracks the newest blend). Pass `ref` = the | |
| # line's release branch; it cuts the next `-N` (base 0.0.37-beta.5 → -1, -2, …), | |
| # publishes, tags, and commits the bump back to that branch. Publishing runs from | |
| # THIS workflow file so it stays within npm's trusted-publisher (OIDC) config. | |
| # A push, or a dispatch with an empty `ref`, behaves exactly like the normal release. | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| ref: | |
| description: "Binding-only re-release: source branch (leave EMPTY for a normal release)" | |
| required: false | |
| default: "" | |
| type: string | |
| dist_tag: | |
| description: "dist-tag override (empty = auto: prerelease→beta, stable→latest)" | |
| required: false | |
| default: "" | |
| type: string | |
| regenerate: | |
| description: "Re-release only: regenerate bindings before publishing" | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: write | |
| packages: write | |
| issues: write | |
| pull-requests: write | |
| id-token: write | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| # Normal release → main (empty input). Re-release → the given branch. | |
| ref: ${{ github.event.inputs.ref || github.ref }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| registry-url: https://registry.npmjs.org/ | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build package | |
| run: npm run build | |
| - name: Check formatting | |
| run: npm run format:check | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: test | |
| permissions: | |
| contents: write | |
| packages: write | |
| issues: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.event.inputs.ref || github.ref }} | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| # Node 24 ships npm 11.12.x natively, which clears the >= 11.5.1 floor that | |
| # npm OIDC trusted publishing requires (https://docs.npmjs.com/trusted-publishers). | |
| # This avoids the fragile `npm install -g npm@latest` upgrade, which can fail | |
| # against a corrupted GitHub toolcache (actions/runner-images#13883). | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| registry-url: "https://registry.npmjs.org" | |
| cache: "npm" | |
| - name: Confirm npm supports OIDC trusted publishing | |
| run: npm --version | |
| - name: Install dependencies | |
| run: npm ci | |
| # Re-release only, opt-in — deterministic regen of the branch's bindings. | |
| - name: Regenerate bindings | |
| if: ${{ github.event.inputs.ref != '' && github.event.inputs.regenerate == 'true' }} | |
| run: npm run generate | |
| - name: Build package | |
| run: npm run build | |
| # Normal release: version = package.json (1:1 with blend), as before. | |
| # Re-release (ref set): compute the next `-N` on top of package.json's base | |
| # version, write it, and refresh the lockfile. | |
| - name: Resolve package name + version + dist-tag | |
| id: meta | |
| run: | | |
| REF="${{ github.event.inputs.ref }}" | |
| NAME=$(node -p "require('./package.json').name") | |
| if [ -n "$REF" ]; then | |
| RAW=$(node -p "require('./package.json').version") | |
| BASE=$(echo "$RAW" | sed -E 's/-[0-9]+$//') | |
| EXISTING=$(npm view "$NAME" versions --json 2>/dev/null || echo "[]") | |
| NEXT=$(EXISTING="$EXISTING" BASE="$BASE" node -e ' | |
| const raw = JSON.parse(process.env.EXISTING || "[]"); | |
| const list = Array.isArray(raw) ? raw : [raw]; | |
| const prefix = process.env.BASE + "-"; | |
| let max = 0; | |
| for (const s of list) { | |
| if (typeof s === "string" && s.startsWith(prefix)) { | |
| const rest = s.slice(prefix.length); | |
| if (/^\d+$/.test(rest)) max = Math.max(max, parseInt(rest, 10)); | |
| } | |
| } | |
| process.stdout.write(String(max + 1)); | |
| ') | |
| VERSION="${BASE}-${NEXT}" | |
| node -e "const p=require('./package.json'); p.version='$VERSION'; require('fs').writeFileSync('./package.json', JSON.stringify(p,null,2)+'\n');" | |
| npm install --package-lock-only | |
| echo "rerelease=true" >> $GITHUB_OUTPUT | |
| echo "Binding-only re-release: $BASE → $VERSION" | |
| else | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "rerelease=false" >> $GITHUB_OUTPUT | |
| fi | |
| echo "name=$NAME" >> $GITHUB_OUTPUT | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| # Dist-tag policy (explicit input wins): | |
| # - Prerelease (e.g. 0.0.37-beta.6) → `beta`, so a beta NEVER moves `latest`. | |
| # - Stable (e.g. 0.0.37) → `latest`, set EXPLICITLY because with 1:1 | |
| # versioning the 0.0.x line is lower than the legacy 1.0.2 still on npm. | |
| DTAG="${{ github.event.inputs.dist_tag }}" | |
| if [ -z "$DTAG" ]; then | |
| if [[ "$VERSION" =~ -(beta|alpha|rc) ]]; then DTAG=beta; else DTAG=latest; fi | |
| fi | |
| if [[ "$DTAG" == "latest" && "$VERSION" =~ -(beta|alpha|rc) ]]; then | |
| echo "Refusing to publish prerelease $VERSION under 'latest'."; exit 1 | |
| fi | |
| echo "tag=$DTAG" >> $GITHUB_OUTPUT | |
| echo "Resolved dist-tag '$DTAG' for $VERSION" | |
| - name: Check npm registry | |
| id: npm | |
| run: | | |
| if npm view "${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}" version >/dev/null 2>&1; then | |
| echo "${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }} already on npm — skipping." | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Publish to npm with OIDC provenance | |
| if: steps.npm.outputs.exists == 'false' | |
| run: | | |
| echo "Publishing ${{ steps.meta.outputs.version }} to npm under dist-tag '${{ steps.meta.outputs.tag }}'..." | |
| npm publish --provenance --access public --tag "${{ steps.meta.outputs.tag }}" | |
| echo "Published to npm successfully!" | |
| # Re-release only: commit the version bump back onto the source branch so its | |
| # package.json records the shipped `-N` (normal releases already carry it). | |
| - name: Commit re-release version bump | |
| if: steps.meta.outputs.rerelease == 'true' && steps.npm.outputs.exists == 'false' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git commit -am "chore(release): binding-only re-release ${{ steps.meta.outputs.version }}" || echo "Nothing to commit." | |
| git push origin "HEAD:${{ github.event.inputs.ref }}" | |
| # Tag AFTER a successful npm publish, and tolerate an existing tag, so a | |
| # mid-run failure can't leave a pushed tag that permanently breaks re-runs. | |
| - name: Create git tag | |
| if: steps.npm.outputs.exists == 'false' | |
| run: | | |
| V="v${{ steps.meta.outputs.version }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| if git ls-remote --tags origin "$V" | grep -q "$V"; then | |
| echo "Tag $V already exists — skipping." | |
| else | |
| git tag "$V" | |
| git push origin "$V" | |
| fi | |
| - name: Setup Node for GitHub Packages | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| registry-url: "https://npm.pkg.github.com" | |
| scope: "@juspay" | |
| # Gated on GitHub Packages' own state (not the npm check) and idempotent — | |
| # so it retries on a later run if a previous run published to npm but failed here. | |
| - name: Publish to GitHub Packages | |
| run: | | |
| NAME="${{ steps.meta.outputs.name }}"; VERSION="${{ steps.meta.outputs.version }}" | |
| if npm view "$NAME@$VERSION" version --registry https://npm.pkg.github.com >/dev/null 2>&1; then | |
| echo "$NAME@$VERSION already on GitHub Packages — skipping." | |
| exit 0 | |
| fi | |
| echo "Publishing to GitHub Packages..." | |
| npm publish --access public --no-git-checks --tag "${{ steps.meta.outputs.tag }}" --registry https://npm.pkg.github.com | |
| echo "Published to GitHub Packages successfully!" | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |