From 30afdd4acc99e3d33c9ae9133ffacc960614418e Mon Sep 17 00:00:00 2001 From: Ivo Date: Wed, 15 Jul 2026 19:53:05 +0200 Subject: [PATCH 1/2] fix(ci): support Node 24 releases and fork previews --- .../workflows/deploy-storybook-preview.yml | 115 ++++++++++++++++++ .github/workflows/deploy-storybook.yml | 64 +++------- .github/workflows/publish.yml | 10 +- 3 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/deploy-storybook-preview.yml diff --git a/.github/workflows/deploy-storybook-preview.yml b/.github/workflows/deploy-storybook-preview.yml new file mode 100644 index 00000000..e1084c7e --- /dev/null +++ b/.github/workflows/deploy-storybook-preview.yml @@ -0,0 +1,115 @@ +name: Deploy Storybook PR Preview + +on: + workflow_run: + workflows: ["Deploy Storybook to GitHub Pages"] + types: [completed] + +# This workflow runs with write access, so it must never check out or execute +# pull request code. It only publishes the static artifact produced by the +# read-only pull_request workflow. +permissions: + actions: read + contents: write + pages: read + pull-requests: write + +jobs: + deploy-preview: + if: >- + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + concurrency: + group: storybook-preview-${{ github.event.workflow_run.head_repository.id }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + + steps: + - name: Checkout trusted deployment workflow + uses: actions/checkout@v4 + with: + ref: main + + # workflow_run.pull_requests is empty for fork runs in this repository. + # Resolve the PR from trusted event/API metadata instead of accepting a + # contributor-controlled PR number from the artifact. + - name: Resolve pull request + id: pr + uses: actions/github-script@v7 + with: + script: | + const run = context.payload.workflow_run; + const headOwner = run.head_repository.owner.login; + const { data: pulls } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + base: 'main', + head: `${headOwner}:${run.head_branch}`, + per_page: 100, + }); + const pull = pulls.find((candidate) => + candidate.head.repo?.id === run.head_repository.id && + candidate.head.sha === run.head_sha + ); + if (!pull) { + core.setFailed(`No open pull request matches workflow run ${run.id}`); + return; + } + core.setOutput('number', pull.number); + + - name: Download Storybook artifact + uses: actions/download-artifact@v4 + with: + name: storybook-static + path: storybook-static + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Get Pages URL + id: pages + uses: actions/github-script@v7 + with: + script: | + const { data } = await github.rest.repos.getPages({ + owner: context.repo.owner, + repo: context.repo.repo, + }); + core.setOutput('url', data.html_url); + + - name: Deploy PR preview + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./storybook-static + publish_branch: gh-pages + destination_dir: pr/${{ steps.pr.outputs.number }} + + - name: Add Storybook link to PR description + uses: actions/github-script@v7 + with: + script: | + const prNumber = Number('${{ steps.pr.outputs.number }}'); + const url = `${{ steps.pages.outputs.url }}pr/${prNumber}/`; + const startMarker = ''; + const endMarker = ''; + const link = `${startMarker}\n---\nšŸ“– **Storybook Preview:** ${url}\n${endMarker}`; + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + const body = pr.body || ''; + const newBody = body.includes(startMarker) + ? body.replace(/[\s\S]*?/, link) + : `${body}\n\n${link}`; + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + body: newBody, + }); + + - name: Print deploy URL + run: echo "::notice::Storybook preview deployed to ${{ steps.pages.outputs.url }}pr/${{ steps.pr.outputs.number }}/" diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml index 4e995259..7e661bfe 100644 --- a/.github/workflows/deploy-storybook.yml +++ b/.github/workflows/deploy-storybook.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main ] +permissions: + contents: write + jobs: build-and-deploy: runs-on: ubuntu-latest @@ -13,19 +16,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup Node.js + - name: Setup Node.js 24 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 9.7.0 - - name: Authenticate npm registry - run: echo "//registry.npmjs.org/:_authToken=${{ secrets.TEST_NPM_TOKEN }}" > ~/.npmrc - - name: Get pnpm store directory shell: bash run: | @@ -48,8 +48,20 @@ jobs: - name: Build Storybook run: pnpm --filter @unlayer/react-elements run build-storybook + # Fork pull requests have a read-only token and no secrets. Upload the + # static build for the trusted workflow_run workflow to deploy instead. + - name: Upload PR preview artifact + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: storybook-static + path: packages/react/storybook-static + if-no-files-found: error + retention-days: 7 + # Get the GitHub Pages URL dynamically - name: Get Pages URL + if: github.event_name == 'push' && github.ref == 'refs/heads/main' id: pages uses: actions/github-script@v7 with: @@ -72,45 +84,3 @@ jobs: - name: Print deploy URL (main) if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: echo "::notice::Storybook deployed to ${{ steps.pages.outputs.url }}" - - # PR → deploy to /pr// subdirectory (doesn't touch main) - - name: Deploy (PR preview) - if: github.event_name == 'pull_request' - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./packages/react/storybook-static - publish_branch: gh-pages - destination_dir: pr/${{ github.event.pull_request.number }} - - - name: Print deploy URL (PR) - if: github.event_name == 'pull_request' - run: echo "::notice::Storybook preview deployed to ${{ steps.pages.outputs.url }}pr/${{ github.event.pull_request.number }}/" - - - name: Add Storybook link to PR description - if: github.event_name == 'pull_request' - uses: actions/github-script@v7 - with: - script: | - const url = `${{ steps.pages.outputs.url }}pr/${{ github.event.pull_request.number }}/`; - const startMarker = ''; - const endMarker = ''; - const link = `${startMarker}\n---\nšŸ“– **Storybook Preview:** ${url}\n${endMarker}`; - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - const body = pr.body || ''; - let newBody; - if (body.includes(startMarker)) { - newBody = body.replace(/[\s\S]*?/, link); - } else { - newBody = `${body}\n\n${link}`; - } - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - body: newBody, - }); diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index eb5c38b3..f4c34862 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -44,10 +44,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Setup Node.js 20 + - name: Setup Node.js 24 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Setup pnpm uses: pnpm/action-setup@v4 @@ -101,14 +101,14 @@ jobs: # setup-node with registry-url enables OIDC token exchange for npm publish. # All @unlayer deps are public — no NPM_TOKEN needed. - - name: Setup Node.js 20 (with npm registry for OIDC) + - name: Setup Node.js 24 (with npm registry for OIDC) uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 registry-url: https://registry.npmjs.org - name: Update npm for trusted publishing - run: npm install -g npm@latest + run: npm install -g npm@12 - name: Get pnpm store directory shell: bash From d42ba612b0a56e9dbc3f195c0675801ec1ad3045 Mon Sep 17 00:00:00 2001 From: Ivo Date: Wed, 15 Jul 2026 20:03:47 +0200 Subject: [PATCH 2/2] revert(ci): defer fork Storybook previews --- .../workflows/deploy-storybook-preview.yml | 115 ------------------ .github/workflows/deploy-storybook.yml | 64 +++++++--- 2 files changed, 47 insertions(+), 132 deletions(-) delete mode 100644 .github/workflows/deploy-storybook-preview.yml diff --git a/.github/workflows/deploy-storybook-preview.yml b/.github/workflows/deploy-storybook-preview.yml deleted file mode 100644 index e1084c7e..00000000 --- a/.github/workflows/deploy-storybook-preview.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Deploy Storybook PR Preview - -on: - workflow_run: - workflows: ["Deploy Storybook to GitHub Pages"] - types: [completed] - -# This workflow runs with write access, so it must never check out or execute -# pull request code. It only publishes the static artifact produced by the -# read-only pull_request workflow. -permissions: - actions: read - contents: write - pages: read - pull-requests: write - -jobs: - deploy-preview: - if: >- - github.event.workflow_run.event == 'pull_request' && - github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - concurrency: - group: storybook-preview-${{ github.event.workflow_run.head_repository.id }}-${{ github.event.workflow_run.head_branch }} - cancel-in-progress: true - - steps: - - name: Checkout trusted deployment workflow - uses: actions/checkout@v4 - with: - ref: main - - # workflow_run.pull_requests is empty for fork runs in this repository. - # Resolve the PR from trusted event/API metadata instead of accepting a - # contributor-controlled PR number from the artifact. - - name: Resolve pull request - id: pr - uses: actions/github-script@v7 - with: - script: | - const run = context.payload.workflow_run; - const headOwner = run.head_repository.owner.login; - const { data: pulls } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - base: 'main', - head: `${headOwner}:${run.head_branch}`, - per_page: 100, - }); - const pull = pulls.find((candidate) => - candidate.head.repo?.id === run.head_repository.id && - candidate.head.sha === run.head_sha - ); - if (!pull) { - core.setFailed(`No open pull request matches workflow run ${run.id}`); - return; - } - core.setOutput('number', pull.number); - - - name: Download Storybook artifact - uses: actions/download-artifact@v4 - with: - name: storybook-static - path: storybook-static - github-token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.repository }} - run-id: ${{ github.event.workflow_run.id }} - - - name: Get Pages URL - id: pages - uses: actions/github-script@v7 - with: - script: | - const { data } = await github.rest.repos.getPages({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - core.setOutput('url', data.html_url); - - - name: Deploy PR preview - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./storybook-static - publish_branch: gh-pages - destination_dir: pr/${{ steps.pr.outputs.number }} - - - name: Add Storybook link to PR description - uses: actions/github-script@v7 - with: - script: | - const prNumber = Number('${{ steps.pr.outputs.number }}'); - const url = `${{ steps.pages.outputs.url }}pr/${prNumber}/`; - const startMarker = ''; - const endMarker = ''; - const link = `${startMarker}\n---\nšŸ“– **Storybook Preview:** ${url}\n${endMarker}`; - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - }); - const body = pr.body || ''; - const newBody = body.includes(startMarker) - ? body.replace(/[\s\S]*?/, link) - : `${body}\n\n${link}`; - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - body: newBody, - }); - - - name: Print deploy URL - run: echo "::notice::Storybook preview deployed to ${{ steps.pages.outputs.url }}pr/${{ steps.pr.outputs.number }}/" diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml index 7e661bfe..4e995259 100644 --- a/.github/workflows/deploy-storybook.yml +++ b/.github/workflows/deploy-storybook.yml @@ -6,9 +6,6 @@ on: pull_request: branches: [ main ] -permissions: - contents: write - jobs: build-and-deploy: runs-on: ubuntu-latest @@ -16,16 +13,19 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup Node.js 24 + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 24 + node-version: 20 - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 9.7.0 + - name: Authenticate npm registry + run: echo "//registry.npmjs.org/:_authToken=${{ secrets.TEST_NPM_TOKEN }}" > ~/.npmrc + - name: Get pnpm store directory shell: bash run: | @@ -48,20 +48,8 @@ jobs: - name: Build Storybook run: pnpm --filter @unlayer/react-elements run build-storybook - # Fork pull requests have a read-only token and no secrets. Upload the - # static build for the trusted workflow_run workflow to deploy instead. - - name: Upload PR preview artifact - if: github.event_name == 'pull_request' - uses: actions/upload-artifact@v4 - with: - name: storybook-static - path: packages/react/storybook-static - if-no-files-found: error - retention-days: 7 - # Get the GitHub Pages URL dynamically - name: Get Pages URL - if: github.event_name == 'push' && github.ref == 'refs/heads/main' id: pages uses: actions/github-script@v7 with: @@ -84,3 +72,45 @@ jobs: - name: Print deploy URL (main) if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: echo "::notice::Storybook deployed to ${{ steps.pages.outputs.url }}" + + # PR → deploy to /pr// subdirectory (doesn't touch main) + - name: Deploy (PR preview) + if: github.event_name == 'pull_request' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./packages/react/storybook-static + publish_branch: gh-pages + destination_dir: pr/${{ github.event.pull_request.number }} + + - name: Print deploy URL (PR) + if: github.event_name == 'pull_request' + run: echo "::notice::Storybook preview deployed to ${{ steps.pages.outputs.url }}pr/${{ github.event.pull_request.number }}/" + + - name: Add Storybook link to PR description + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const url = `${{ steps.pages.outputs.url }}pr/${{ github.event.pull_request.number }}/`; + const startMarker = ''; + const endMarker = ''; + const link = `${startMarker}\n---\nšŸ“– **Storybook Preview:** ${url}\n${endMarker}`; + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + const body = pr.body || ''; + let newBody; + if (body.includes(startMarker)) { + newBody = body.replace(/[\s\S]*?/, link); + } else { + newBody = `${body}\n\n${link}`; + } + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + body: newBody, + });