diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index 31135cfb4b..173b4db9df 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -342,9 +342,70 @@ jobs: # Set port for output echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT + # ---- Storybook preview (only when this PR touches stories/.storybook) ---- + # Runs inside the same approved-contributor-gated deploy job, so it deploys + # under the exact same access rules as the app preview. + - name: Detect Storybook changes + id: sb-changes + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + with: + list-files: json + filters: | + storybook: + - 'frontend/**/*.stories.@(ts|tsx|mdx)' + - 'frontend/**/*.mdx' + - 'frontend/.storybook/**' + + - name: Set up Node.js for Storybook + if: steps.sb-changes.outputs.storybook == 'true' + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install Task for Storybook + if: steps.sb-changes.outputs.storybook == 'true' + uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 + + - name: Build and deploy Storybook + id: storybook + if: steps.sb-changes.outputs.storybook == 'true' + env: + VPS_HOST: ${{ secrets.NEW_VPS_HOST }} + VPS_USER: ${{ secrets.NEW_VPS_USERNAME }} + run: | + set -euo pipefail + # `prepare` generates the icon set stories import (not committed). + task frontend:prepare + task frontend:storybook:build + PR=${{ needs.check-pr.outputs.pr_number }} + # Served at the ROOT of its own port so Storybook's global MSW worker + # (/mockServiceWorker.js) resolves. Port = PR + 20000 (bijective, offset + # from the app preview's bare-PR-number port). + SB_PORT=$((PR + 20000)) + DIR=/stirling/SB-PR-$PR + tar czf storybook.tgz -C frontend/storybook-static . + scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + storybook.tgz "$VPS_USER@$VPS_HOST:/tmp/storybook-$PR.tgz" + ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T \ + "$VPS_USER@$VPS_HOST" << ENDSSH + set -e + rm -rf "$DIR" && mkdir -p "$DIR" + tar xzf /tmp/storybook-$PR.tgz -C "$DIR" + rm -f /tmp/storybook-$PR.tgz + docker rm -f storybook-pr-$PR 2>/dev/null || true + docker run -d --name storybook-pr-$PR --restart unless-stopped \ + -p $SB_PORT:80 -v "$DIR":/usr/share/nginx/html:ro nginx:alpine + ENDSSH + echo "url=http://$VPS_HOST:$SB_PORT/" >> "$GITHUB_OUTPUT" + - name: Post V2 deployment URL to PR if: success() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + SB_URL: ${{ steps.storybook.outputs.url }} + SB_FILES: ${{ steps.sb-changes.outputs.storybook_files }} with: github-token: ${{ steps.setup-bot.outputs.token }} script: | @@ -376,11 +437,32 @@ jobs: ? `🧩 **Admin portal** included - try it at [${deploymentUrl}/portal](${deploymentUrl}/portal).\n\n` : ``; + // Storybook preview: only present when this PR changed stories/config. + const sbUrl = process.env.SB_URL; + let storybookNote = ""; + if (sbUrl) { + const files = JSON.parse(process.env.SB_FILES || "[]"); + const stories = files.filter((f) => /\.stories\.(ts|tsx|mdx)$/.test(f)); + const config = files.filter((f) => f.startsWith("frontend/.storybook/")); + const shorten = (f) => + f.replace(/^frontend\/editor\/src\//, "").replace(/^frontend\//, ""); + const storyList = stories.map((f) => `- \`${shorten(f)}\``).join("\n"); + const configList = config.map((f) => `- \`${shorten(f)}\``).join("\n"); + const summary = + `${stories.length} stor${stories.length === 1 ? "y" : "ies"} changed` + + (config.length ? ` (+${config.length} config file${config.length === 1 ? "" : "s"})` : ""); + storybookNote = + `📚 **Storybook:** [${sbUrl}](${sbUrl})\n\n` + + `
\n${summary}\n\n` + + (storyList ? `**Stories**\n${storyList}\n\n` : "") + + (configList ? `**Config**\n${configList}\n` : "") + + `
\n\n`; + } + const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` + - `Your V2 PR with embedded architecture has been deployed!\n\n` + `🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` + - `🔐 **Secure HTTPS URL**: unsupported currently\n\n` + portalNote + + storybookNote + `_This deployment will be automatically cleaned up when the PR is closed._\n\n` + `🔄 **Auto-deployed** for approved V2 contributors.`; @@ -476,7 +558,11 @@ jobs: else echo "V2 PR directory not found, nothing to clean up" fi - + + # Remove this PR's Storybook preview (container + files), if any. + docker rm -f storybook-pr-${{ github.event.pull_request.number }} 2>/dev/null || true + rm -rf /stirling/SB-PR-${{ github.event.pull_request.number }} + # Clean up old unused images (older than 2 weeks) but keep recent ones for reuse docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true diff --git a/frontend/editor/src/core/ui/Banner.stories.tsx b/frontend/editor/src/core/ui/Banner.stories.tsx index 891cf66b78..2e1f2b4d78 100644 --- a/frontend/editor/src/core/ui/Banner.stories.tsx +++ b/frontend/editor/src/core/ui/Banner.stories.tsx @@ -33,6 +33,15 @@ type Story = StoryObj; /** Flip tone / title / description / action / onDismiss in controls. */ export const Playground: Story = {}; +/** Success tone - added as a Storybook-preview workflow example. */ +export const Success: Story = { + args: { + tone: "success", + title: "All clear", + description: "Every document passed validation.", + }, +}; + export const WithAction: Story = { args: { tone: "warning", diff --git a/frontend/editor/src/core/ui/Chip.stories.tsx b/frontend/editor/src/core/ui/Chip.stories.tsx index 22a9f554c8..6fab3f8e85 100644 --- a/frontend/editor/src/core/ui/Chip.stories.tsx +++ b/frontend/editor/src/core/ui/Chip.stories.tsx @@ -1,3 +1,4 @@ +// One-line edit to demonstrate the storybook-preview workflow detects edits, not just adds. import type { Meta, StoryObj } from "@storybook/react-vite"; import { Chip } from "@app/ui/Chip";