Skip to content

chore(CI): factor the repeated job setup into a composite action - #338

Merged
soroushm merged 6 commits into
mainfrom
chore/ci-setup-composite-action
Aug 12, 2026
Merged

chore(CI): factor the repeated job setup into a composite action#338
soroushm merged 6 commits into
mainfrom
chore/ci-setup-composite-action

Conversation

@soroushm

@soroushm soroushm commented Aug 9, 2026

Copy link
Copy Markdown
Member

Closes #330.

Two commits, each standing on its own.

1. f9b7100 — the composite action

Eight CI jobs opened with the same four steps, so bumping the pnpm pin or changing the store cache was eight identical edits with one file always at risk of being left behind. .github/actions/setup now holds pnpm, Node and the install, and every job calls it:

- name: Checkout Repository
  uses: actions/checkout@v5
  with:
    persist-credentials: false

- name: Setup pnpm, Node and dependencies
  uses: ./.github/actions/setup
  with:
    node_version: ${{ inputs.node_version }}
    manager: ${{ inputs.manager }}
    command: ${{ inputs.command }}

The checkout stays in the job, which is a correction to the issue's acceptance criteria — see the comment on #330. A local action is resolved from the working tree, so uses: ./.github/actions/setup cannot carry the checkout that puts it there. It is also the step that varies: ci-web.yml's web job needs fetch-depth: 0 for Codecov base detection.

Per-area caches stay in their jobs, moved just below the call — Playwright binaries in three jobs, the Electron binary in two. Nothing writes either path during an install, the Electron binary is fetched by the explicit rebuild electron step because the shared install runs --ignore-scripts, and each restore only has to precede the steps that read it.

The install passes its arguments through env and a fixed dispatch rather than splicing them into the run body, the same shape ci-editor.yml already uses for the Electron rebuild.

Change detection had to learn about the new file

scripts/assemble-changes.mjs built its filter keys by scanning .github/workflows/ only, so a PR touching just the new action matched no key: every area job skipped and ci-ok green, on a change that alters how every job installs — this issue's own failure mode in a new shape.

The action now goes through the same wf__ channel as a workflow, with its own # ci:validates all marker. That re-runs every job while leaving changes.root false, so a CI edit still deploys nothing. Verified against the previous script: the only differences are the added filter key and a log line that now prints a path.

filters added  : [ 'wf__actions-setup' ]     removed/changed: none
marker diffs over 13 existing workflows: 0
keys equal: true · wholeWorkspace equal: true
action-only change -> changes.root false, 2 apps + 12 packages + 2 workers run

2. 2c8979e — one naming scheme for the Actions list

The five area workflows already read CI · <Area>; the entry workflow and four of the five deploys did not, so the sidebar was a list to read rather than two blocks to scan.

File Was Now
ci.yml Continuous Integration CI
cd-web.yml Continuous Deployment to GitHub Pages CD · Web (Pages + Storybook)
cd-worker-api.yml Deploy Worker (@soroush/api) CD · Worker (api)
cd-worker-bench.yml Deploy Worker (@soroush/bench-api) CD · Worker (bench)
cd-packages.yml Publish Packages (npm) CD · Packages (npm)
cd-editor.yml CD · Editor CD · Editor (release)

Chromatic and the labeller stay unprefixed — neither is part of ci-ok, and a CI prefix would say they gate a pull request when they do not.

The load-bearing part of this commit is the lockstep edit. workflow_run matches on a workflow's name:, never its filename, so cd-web, cd-worker-api and cd-worker-bench all move to workflows: ['CI'] in the same change. Miss one and that deploy stops firing with no error. Verified every reference resolves to a workflow that exists. Thirteen doc references move with them, including the cd-packages runbook step naming the entry to click in the Actions UI.

Branch protection is unaffected — it matches the job name ci-ok.

Review notes

  • ci.yml's concurrency group is keyed on github.workflow, so its value changes with the rename. The group stays internally consistent either side; a run in flight at merge time will not be superseded by the first run after.
  • The Windows e2e row now installs under Git Bash rather than pwsh. Composite run steps must declare a shell:, so the runner default cannot be kept. The arguments are all -- flags, so path mangling should not apply — but this is the one thing only a real run confirms. Worth watching the firefox row.
  • Docs: a "The setup action" section in ci.md, a pointer in the workflows README, and rules in the ci-cd skill covering both the action and the workflow_run naming coupling, so the next person starts from them.

Verification

  • pnpm lint clean; full test suite green on both commits via the pre-commit hook (232 files, 1372 tests)
  • All 13 workflows and the action parse, with the expected jobs and steps
  • prettier --check clean across everything touched
  • No test run for scripts/assemble-changes.mjs — it sits in no vitest project and has never had one. Verified by executing the old and new versions side by side instead. Worth its own task if we want that script covered; it is load-bearing enough to deserve it.

Out of scope, worth a follow-up

The duplication this issue predicts has already drifted outside CI. Seven more setup blocks live in cd-*.yml and chromatic.yml, running two different pnpm/action-setup pins (fc06bc12… v5 and 0ebf4713… v6.0.9), three actions/checkout variants and three actions/setup-node variants. Extending the action there needs node-version-file: .nvmrc support plus a pin reconciliation on deploy paths — its own task rather than riding along here.

Summary by CodeRabbit

  • New Features

    • Centralized workflow setup now configures Node.js, package managers, caching, and dependencies consistently.
    • CI/CD workflows use standardized names for testing and deployment activities.
  • Improvements

    • Workflow change detection now covers automation changes and safely handles unrecognized or deleted workflow files.
  • Documentation

    • Updated CI/CD guidance, release instructions, workflow references, and diagrams to reflect revised names, triggers, setup, and deployment conditions.

 #330

Eight CI jobs opened with the same four steps, so bumping the pnpm pin or
changing the store cache was eight identical edits with one file always at risk
of being left behind. `.github/actions/setup` now holds pnpm, Node and the
install, and every job calls it.

The checkout stays in the job. A local action is resolved from the working tree,
so `uses: ./.github/actions/setup` cannot carry the checkout that puts it there
— and checkout is the step that varies anyway, since ci-web.yml's `web` job needs
`fetch-depth: 0` for Codecov base detection.

Per-area caches stay in their jobs, moved just below the call: nothing writes the
Playwright path during an install, and the Electron binary is fetched by the
explicit `rebuild electron` step because the shared install runs --ignore-scripts.
Both restores only have to precede the steps that read them.

The install passes the manager and its flags through env and a fixed dispatch
rather than splicing them into the run body — the same shape ci-editor.yml
already uses for the Electron rebuild.

Change detection had to learn about the new file. `buildFilters()` discovered CI
files by scanning .github/workflows only, so a change to the action alone matched
no filter key: every area job skipped and ci-ok green, on a change that alters how
every job installs. It now goes through the `wf__` channel with its own
`# ci:validates all` marker, which re-runs every job while leaving `changes.root`
false — a CI edit must not deploy the site and both workers.

Verified against the previous script: the only differences are the added filter
key and a log line that now prints a path, since not every entry is `<name>.yml`.
The five area workflows already read `CI · <Area>`, but the entry workflow and
four of the five deploys did not — `Continuous Integration`, `Continuous
Deployment to GitHub Pages`, `Deploy Worker (@soroush/api)`, `Publish Packages
(npm)` — so the sidebar was a list to read rather than two blocks to scan.

  ci.yml               Continuous Integration                 -> CI
  cd-web.yml           Continuous Deployment to GitHub Pages  -> CD · Web (Pages + Storybook)
  cd-worker-api.yml    Deploy Worker (@soroush/api)           -> CD · Worker (api)
  cd-worker-bench.yml  Deploy Worker (@soroush/bench-api)     -> CD · Worker (bench)
  cd-packages.yml      Publish Packages (npm)                 -> CD · Packages (npm)
  cd-editor.yml        CD · Editor                            -> CD · Editor (release)

Chromatic and the labeller stay unprefixed. Neither is part of ci-ok, and a CI
prefix would say they gate a pull request when they do not.

Renaming ci.yml is not a one-file edit: `workflow_run` matches on a workflow's
`name:`, never its filename, so cd-web, cd-worker-api and cd-worker-bench all
move from `workflows: ['Continuous Integration']` to `workflows: ['CI']` in the
same change. Miss one and that deploy stops firing without an error. Thirteen
doc references move with them, including the cd-packages runbook step that names
the entry to click in the Actions UI.

Branch protection is unaffected — it matches the job name `ci-ok`. ci.yml's
concurrency group is keyed on `github.workflow`, so its value changes; the group
stays internally consistent either side of the rename.

The naming rule and the workflow_run coupling are written down in the workflows
README and the ci-cd skill, so the next rename starts from them.
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a shared dependency setup action, updates CI jobs to use it, standardizes CI/CD workflow names and triggers, updates documentation, and extends workflow change detection.

Changes

CI/CD standardization

Layer / File(s) Summary
Shared setup action
.github/actions/setup/action.yml
The composite action configures the selected package manager, Node.js caching, and dependency installation.
CI setup adoption
.github/workflows/ci*.yml, .github/workflows/ci.md, .claude/skills/ci-cd/SKILL.md
CI jobs use the shared setup action. Area-specific caches and existing commands remain in the workflows.
Workflow change detection
scripts/assemble-changes.mjs, .github/workflows/ci.yml, .github/workflows/ci.md
Change assembly discovers CI files, builds filters, resolves workflow paths, and revalidates the workspace for unattributed or deleted CI changes.
Workflow naming and documentation
.github/workflows/*.yml, .github/workflows/*.md, .github/workflows/README.md, .claude/skills/ci-cd/SKILL.md, .claude/skills/release-notes/SKILL.md
CI and CD workflows, triggers, navigation instructions, deployment conditions, diagrams, and guidance use standardized names.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CIWorkflow
  participant SetupAction
  participant PackageManager
  participant AreaCache
  CIWorkflow->>SetupAction: pass Node version, manager, and install command
  SetupAction->>PackageManager: configure tooling and install dependencies
  PackageManager-->>CIWorkflow: dependencies ready
  CIWorkflow->>AreaCache: restore area-specific cache
Loading

Possibly related issues

  • soroush-tech/core issue 330 — Adds and adopts the shared setup composite action across CI jobs.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: moving repeated CI job setup into a shared composite action.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/ci-setup-composite-action

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/README.md:
- Around line 225-227: Update the bench deployment summary around the
`cd-worker-bench.yml` reference to include the `packages∋wrangler-tools`
condition alongside `worker∋bench ∥ root`, keeping it aligned with
`cd-worker-bench.md`; only change the detailed document instead if that package
trigger was intentionally removed.

In `@scripts/assemble-changes.mjs`:
- Around line 59-65: Update ciFiles() and the buildFilters()/workflowValidates()
flow so deleted .github/workflows/<name>.yml paths still produce a corresponding
wf__<name> filter, or trigger a conservative whole-workspace validation
catch-all. Ensure the fallback handles deletions that are absent from ciFiles(),
not only workflow names already detected.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bf450e55-3671-4d70-9bc0-4992c3dc1e59

📥 Commits

Reviewing files that changed from the base of the PR and between 784fb89 and 2c8979e.

📒 Files selected for processing (21)
  • .claude/skills/ci-cd/SKILL.md
  • .claude/skills/release-notes/SKILL.md
  • .github/actions/setup/action.yml
  • .github/workflows/README.md
  • .github/workflows/cd-editor.md
  • .github/workflows/cd-editor.yml
  • .github/workflows/cd-packages.md
  • .github/workflows/cd-packages.yml
  • .github/workflows/cd-web.md
  • .github/workflows/cd-web.yml
  • .github/workflows/cd-worker-api.md
  • .github/workflows/cd-worker-api.yml
  • .github/workflows/cd-worker-bench.md
  • .github/workflows/cd-worker-bench.yml
  • .github/workflows/ci-editor.yml
  • .github/workflows/ci-packages.yml
  • .github/workflows/ci-web.yml
  • .github/workflows/ci-worker.yml
  • .github/workflows/ci.md
  • .github/workflows/ci.yml
  • scripts/assemble-changes.mjs

Comment thread .github/workflows/README.md
Comment thread scripts/assemble-changes.mjs
…ondition

Two review findings.

`attributeWorkflows` built its reason strings from a template literal nested
inside another one, which is hard to read at the point where it matters least.
The path and the meaning are now named, and the reason is one flat template.
Output is byte-identical.

The workflows README summarised the bench deploy as `worker∋bench ∥ root`, but
cd-worker-bench.yml has gated on `packages∋wrangler-tools` since the deploy
started rendering wrangler.json with the shared bin — as cd-worker-bench.md
already documented. The overview was the only place saying otherwise.
The per-file `wf__` keys are built by reading the working tree, so a deleted
workflow or action generates no key: the one thing that changed matched nothing.
A pull request that only removes CI files therefore ran no job at all and
reported green — the same silent under-run the marker parser already refuses to
make when it cannot read a claim.

`ci__any` covers `.github/workflows/**` and `.github/actions/**`. When it matches
and no per-file key does, the change is a file that is no longer there, and the
whole workspace is validated. `changes.root` stays false, so a deletion still
ships nothing.

Coarse on purpose: it knows a file went, not which one, so it says "all" rather
than guessing. A deletion alongside an edit needs no help, since the edit makes
its own claim.

Deleting only a workflow now runs both apps, all twelve packages and both
workers, with root false. Editing one still validates just that file's areas,
and a `cd-*` edit still validates nothing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.md (1)

203-208: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clarify the setup block context.

inputs.* is valid in the current reusable workflows, but not in a normal job such as ci.yml's lint job. Label this block as reusable-workflow-only and add the needs.prepare.outputs.* form for normal jobs, or provide separate examples.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.md around lines 203 - 208, Clarify the “Setup pnpm,
Node and dependencies” example as reusable-workflow-only, and provide a separate
normal-job example using needs.prepare.outputs.node_version,
needs.prepare.outputs.manager, and needs.prepare.outputs.command for the lint
job context.

Source: Coding guidelines

🧹 Nitpick comments (1)
scripts/assemble-changes.mjs (1)

475-480: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add CI filter regression fixtures.

Assert that ci__any and ci__deleted never enter changes.json. Assert that ci__deleted sets revalidateAll and enables full CI validation. Do not require CI gating outputs to remain unchanged; revalidateAll intentionally enables them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/assemble-changes.mjs` around lines 475 - 480, Update the regression
fixtures around assembleChanges to verify that ci__any and ci__deleted are
excluded from changes.json, while ci__deleted sets revalidateAll and enables
full CI validation. Do not assert that CI gating outputs remain unchanged when
revalidateAll is enabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/assemble-changes.mjs`:
- Around line 99-110: The current hasUnattributedCiFile flow cannot detect mixed
changes because CHANGES contains filter names rather than paths. Update assemble
and its caller to pass ci__any_files using list-files: json, then compare the
returned paths against ciFiles() so an existing unclaimed CI file alongside an
edited workflow sets revalidateAll; add a regression case for this combination.
If path plumbing is not feasible, make every CI_ANY match trigger full
validation.

---

Outside diff comments:
In @.github/workflows/ci.md:
- Around line 203-208: Clarify the “Setup pnpm, Node and dependencies” example
as reusable-workflow-only, and provide a separate normal-job example using
needs.prepare.outputs.node_version, needs.prepare.outputs.manager, and
needs.prepare.outputs.command for the lint job context.

---

Nitpick comments:
In `@scripts/assemble-changes.mjs`:
- Around line 475-480: Update the regression fixtures around assembleChanges to
verify that ci__any and ci__deleted are excluded from changes.json, while
ci__deleted sets revalidateAll and enables full CI validation. Do not assert
that CI gating outputs remain unchanged when revalidateAll is enabled.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e57070da-082d-492b-9916-fd7254d1b779

📥 Commits

Reviewing files that changed from the base of the PR and between 8ff77a0 and 2659e6b.

📒 Files selected for processing (2)
  • .github/workflows/ci.md
  • scripts/assemble-changes.mjs

Comment thread scripts/assemble-changes.mjs Outdated
@soroush-bench

soroush-bench Bot commented Aug 11, 2026

Copy link
Copy Markdown

Benchmark results

Baseline case: previous · minimum speed ratio: 80%

packages/styled-system/bench/color.bench.ts — ✅ passed

case avg p75 alloc/iter vs fastest
styled-system color() :: previous 400 ns 400 ns 248 B (+0.0%) fastest
styled-system color() :: local-mjs 401 ns 402 ns 248 B (+0.0%) +0.4%
styled-system color() :: local-cjs 404 ns 405 ns 248 B (least) +1.1%

@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

The key heuristic went blind past the first claim: a CI file no per-file
key names — an aux file inside an action, or a deleted workflow with the
predicate filter gone — changed beside an ordinary workflow edit produced
the same matched keys as the edit alone, and the edit's claim can be far
narrower than what the masked file validated. paths-filter now hands
assemble the paths ci__any matched (list-files: json); anything outside
ciFiles() triggers full validation and is named in the log, a list that
fails to arrive counts as everything, and ci__deleted stays as the
deletion backstop. The .md companions are exempt both solo and mixed:
they are documentation no job reads, and a README typo ran every job.
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/assemble-changes.mjs (1)

475-500: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add CLI regression tests for the CI fallback cases.

Cover invalid CI_ANY_FILES, an unclaimed CI path with a claimed workflow edit, and ci__deleted with another CI edit. Assert that all jobs revalidate and changes.json keeps root: false.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/assemble-changes.mjs` around lines 475 - 500, add CLI regression
tests for the CI fallback handling around unclaimedCiPaths and assembleChanges,
covering invalid CI_ANY_FILES, an unclaimed CI path alongside a claimed workflow
edit, and ci__deleted alongside another CI edit. For each scenario, assert that
all jobs revalidate and the generated changes.json preserves root: false.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/assemble-changes.mjs`:
- Around line 475-500: add CLI regression tests for the CI fallback handling
around unclaimedCiPaths and assembleChanges, covering invalid CI_ANY_FILES, an
unclaimed CI path alongside a claimed workflow edit, and ci__deleted alongside
another CI edit. For each scenario, assert that all jobs revalidate and the
generated changes.json preserves root: false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ff666c76-04c6-41c8-9c7e-b2ac6bc2c3fe

📥 Commits

Reviewing files that changed from the base of the PR and between 2659e6b and b80313a.

📒 Files selected for processing (3)
  • .github/workflows/ci.md
  • .github/workflows/ci.yml
  • scripts/assemble-changes.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.md

@soroushm
soroushm merged commit 6323cad into main Aug 12, 2026
30 checks passed
@soroushm
soroushm deleted the chore/ci-setup-composite-action branch August 12, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task] CI: factor the repeated job setup into a composite action

1 participant