feat: Storybook component diff notifications via GitHub Issues#3708
feat: Storybook component diff notifications via GitHub Issues#3708
Conversation
Adds an automated system that detects when components are added or removed from Storybook and opens a GitHub Issue with the summary. - scripts/storybook-diff.mjs: fetches ds.factorial.dev/index.json, diffs against a local snapshot, and opens a GitHub Issue when new or removed components are detected - scripts/storybook-snapshot.json: baseline snapshot of 314 components - build-and-deploy-storybook.yaml: new job that runs after deploy, uses GITHUB_TOKEN (no extra secrets needed)
There was a problem hiding this comment.
Pull request overview
Adds automated detection of Storybook component additions/removals after each successful deploy and notifies the team via a GitHub Issue, while keeping a committed baseline snapshot in-sync.
Changes:
- Add
scripts/storybook-diff.mjsto fetch/index.json, extract component inventory, diff against a snapshot, and open a GitHub Issue on changes. - Add
scripts/storybook-snapshot.jsonas the initial committed baseline (314 components). - Extend
.github/workflows/build-and-deploy-storybook.yamlwith a post-deploy job that runs the diff, opens an Issue, and auto-commits the updated snapshot.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
scripts/storybook-diff.mjs |
New script to compute component-level diffs and create a GitHub Issue. |
scripts/storybook-snapshot.json |
Baseline snapshot used for future diffs. |
.github/workflows/build-and-deploy-storybook.yaml |
Adds CI job to run the diff post-deploy and commit updated snapshot. |
You can also share your feedback on Copilot code review. Take the survey.
| function buildIssueBody(changes, commitSha, repo) { | ||
| const { added, removed } = changes | ||
| const commitUrl = `https://github.com/${repo}/commit/${commitSha}` | ||
| const lines = [] | ||
|
|
||
| if (added.length > 0) { | ||
| lines.push("## New components\n") | ||
| for (const c of added) { | ||
| lines.push(`- \`${c.name}\` — \`${c.category}\``) | ||
| } | ||
| lines.push("") | ||
| } | ||
|
|
||
| if (removed.length > 0) { | ||
| lines.push("## Removed components\n") | ||
| for (const c of removed) { | ||
| lines.push(`- \`${c.name}\` — \`${c.category}\``) | ||
| } | ||
| lines.push("") | ||
| } | ||
|
|
||
| lines.push("---") | ||
| lines.push(`Triggered by [${commitSha.slice(0, 7)}](${commitUrl}) · [View Storybook](https://ds.factorial.dev)`) | ||
|
|
| permissions: | ||
| contents: read | ||
| contents: write | ||
| pages: write | ||
| id-token: write |
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
|
|
||
| - name: Run Storybook diff and open GitHub Issue | ||
| run: | | ||
| node scripts/storybook-diff.mjs \ | ||
| --storybook-url https://ds.factorial.dev \ | ||
| --snapshot scripts/storybook-snapshot.json \ | ||
| --github-token "${{ secrets.GITHUB_TOKEN }}" \ | ||
| --github-repo "${{ github.repository }}" \ | ||
| --commit-sha "${{ github.sha }}" | ||
|
|
||
| - name: Commit updated snapshot | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git add scripts/storybook-snapshot.json | ||
| git diff --staged --quiet || git commit -m "chore: update storybook component snapshot [skip ci]" | ||
| git push |
| function fetchJson(url, options = {}) { | ||
| return new Promise((resolve, reject) => { | ||
| const parsed = new URL(url) | ||
| const reqOptions = { | ||
| hostname: parsed.hostname, | ||
| path: parsed.pathname + parsed.search, | ||
| method: options.method ?? "GET", | ||
| headers: options.headers ?? {}, | ||
| } | ||
|
|
||
| let body = null | ||
| if (options.body) { | ||
| body = JSON.stringify(options.body) | ||
| reqOptions.headers["Content-Type"] = "application/json" | ||
| reqOptions.headers["Content-Length"] = Buffer.byteLength(body) | ||
| } | ||
|
|
||
| const req = https.request(reqOptions, (res) => { | ||
| let data = "" | ||
| res.on("data", (chunk) => (data += chunk)) | ||
| res.on("end", () => { | ||
| try { | ||
| resolve(JSON.parse(data)) | ||
| } catch (e) { | ||
| resolve(data) | ||
| } | ||
| }) | ||
| }) | ||
| req.on("error", reject) | ||
| if (body) req.write(body) | ||
| req.end() | ||
| }) |
| function fetchJson(url, options = {}) { | ||
| return new Promise((resolve, reject) => { | ||
| const parsed = new URL(url) | ||
| const reqOptions = { | ||
| hostname: parsed.hostname, | ||
| path: parsed.pathname + parsed.search, | ||
| method: options.method ?? "GET", | ||
| headers: options.headers ?? {}, | ||
| } | ||
|
|
||
| let body = null | ||
| if (options.body) { | ||
| body = JSON.stringify(options.body) | ||
| reqOptions.headers["Content-Type"] = "application/json" | ||
| reqOptions.headers["Content-Length"] = Buffer.byteLength(body) | ||
| } | ||
|
|
||
| const req = https.request(reqOptions, (res) => { |
| for (const entry of Object.values(entries)) { | ||
| const title = entry.title ?? "" | ||
| const parts = title.split("/") | ||
| const name = parts[parts.length - 1] | ||
| const category = parts.slice(0, -1).join("/") | ||
|
|
||
| if (!components[name]) { | ||
| components[name] = { category, stories: [] } | ||
| } | ||
| components[name].stories.push(entry.name ?? entry.id) | ||
| } |
… Tweaks with direct story URLs
…New Atoms/Low Impact with /docs/ links
There was a problem hiding this comment.
Pull request overview
Adds automated detection and notification of Storybook component inventory changes by diffing the deployed Storybook index against a committed snapshot, and reporting additions/removals via GitHub Issues.
Changes:
- Add
scripts/storybook-diff.mjsto fetchindex.json, compute a diff vs a snapshot, and open a GitHub Issue. - Add a baseline snapshot at
scripts/storybook-snapshot.json. - Extend the Storybook deploy workflow to run the diff after deploy and auto-commit snapshot updates.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
scripts/storybook-diff.mjs |
New Node script to extract components from Storybook, diff against snapshot, and create an issue. |
scripts/storybook-snapshot.json |
Baseline committed snapshot of Storybook “components” inventory. |
.github/workflows/build-and-deploy-storybook.yaml |
New post-deploy job to run the diff script and commit updated snapshots back to the repo. |
You can also share your feedback on Copilot code review. Take the survey.
| res.on("data", (chunk) => (data += chunk)) | ||
| res.on("end", () => { | ||
| try { | ||
| resolve(JSON.parse(data)) | ||
| } catch (e) { | ||
| resolve(data) |
| for (const entry of Object.values(entries)) { | ||
| const title = entry.title ?? "" | ||
| const parts = title.split("/") | ||
| const name = parts[parts.length - 1] | ||
| const category = parts.slice(0, -1).join("/") | ||
|
|
||
| if (!components[name]) { | ||
| // Store the first story id to build a direct Storybook URL later | ||
| components[name] = { | ||
| category, | ||
| tags: entry.tags ?? [], | ||
| storyId: entry.id ?? "", | ||
| } | ||
| } |
| // Convert story id to docs id: strip the trailing story variant | ||
| // e.g. "components-f0button--default" → "components-f0button--docs" | ||
| const docsId = storyId.replace(/--[^-]+$/, "--docs") |
| lines.push("## 🔴 Breaking Changes") | ||
| lines.push( | ||
| "_Componentes `stable` eliminados o marcados como `deprecated`. Revisa tus diseños — pueden estar usando algo que ya no existe._\n" | ||
| ) | ||
| for (const c of breaking) { | ||
| const reason = degraded.includes(c) ? "degraded to `deprecated`" : "removed" | ||
| lines.push(`- **[${c.name}](${c.url})** \`${c.category}\` — ${reason}`) | ||
| } | ||
| lines.push("") | ||
| } | ||
|
|
||
| // ── 2. Visual Tweaks (Foundations) ────────────────────────────────────── | ||
| if (visual.length > 0) { | ||
| lines.push("## 🎨 Visual Tweaks — Foundations") | ||
| lines.push( | ||
| "_Cambios en tokens de diseño (colores, espaciado, tipografía, bordes, sombras, iconos). Impacto directo en todos los diseños._\n" | ||
| ) |
| ) | ||
| for (const c of lowImpact) { | ||
| const direction = added.includes(c) ? "added" : "removed" | ||
| lines.push(`- **[${c.name}](${c.url})** \`${c.category}\` — ${direction}`) |
| permissions: | ||
| contents: read | ||
| contents: write | ||
| pages: write | ||
| id-token: write |
Summary
scripts/storybook-diff.mjs) that fetches the live Storybook index and diffs it against a committed snapshot to detect when components are added or removedscripts/storybook-snapshot.json) of 314 components.github/workflows/build-and-deploy-storybook.yaml) with a newnotify-storybook-changesjob that runs after each successful Storybook deploy, opens a GitHub Issue if components changed, and auto-commits the updated snapshot with[skip ci]Why
The design system team needs to stay informed of structural changes to the F0 component library without manually checking Storybook. This automates that signal with low noise — only component-level additions/removals trigger a notification, not story-level changes within existing components.
How it works
https://ds.factorial.dev, thenotify-storybook-changesjob runshttps://ds.factorial.dev/index.jsonand extracts the list of unique component namesstorybook-snapshot.jsonmainwith[skip ci]to avoid a loopNotes
GITHUB_TOKEN— no additional secrets required