Skip to content

ci: split the npm pipeline into reusable build + thin CI/release callers - #1600

Draft
nebasuke wants to merge 4 commits into
ci/mirror-waitfrom
ci/npm-release-split
Draft

ci: split the npm pipeline into reusable build + thin CI/release callers#1600
nebasuke wants to merge 4 commits into
ci/mirror-waitfrom
ci/npm-release-split

Conversation

@nebasuke

@nebasuke nebasuke commented Aug 3, 2026

Copy link
Copy Markdown
Member

Claude summary

Stacked on #1599. Second half of #1593: the structural follow-up to @anaPerezGhiglia's #1587 review comment:

the trick of bypassing steps with the tag output is growing too much and I fear it because I think it's too easy to make a mistake with it. I think we should re-think this workflow and gate that in a more robust way.

edr-npm-release.yml did double duty as PR validation and release pipeline, with the release/non-release mode threaded through job- and step-level conditions — easy to miss on a new step, and a dropped guard failed silently toward the unsafe side (warm poisonable cache, mirror image in the release supply chain).

New shape

  • edr-npm-build.yml (workflow_call) — the build matrix, binding tests, prepare, review, moved verbatim. One typed release: boolean input is the only mode switch: cargo cache vs cold build (still runtime-asserted from ci: wait for in-flight mirror runs; consolidate release gating on is_release #1599), GHCR mirror vs official Docker Hub image. Publishing can never happen here.
  • edr-npm-ci.yml — thin entry for PRs/branch pushes: calls the build workflow with release: false. Skips itself on release pushes so those don't double-build.
  • edr-npm-release.yml — thin release pipeline: release-commit detection, then everything hangs off a single release_gate job — the only release condition in the file. cooldown-check, notify-deploy, publish carry no guards; adding a job can't miss one.
  • check-release-commit composite — the commit-message detection, shared by both entries instead of duplicated.

The callee was produced by copy + trim from the monolith; the diff of the moved jobs is limited to the header, five is-release: ${{ inputs.release }} call sites, two cache-gate ifs, and dropped check_commit needs entries — everything else is byte-identical.

Release rehearsal (workflow_dispatch)

Manually dispatching edr-npm-release.yml now forces the full release path on any commit — cold build, Docker Hub images, cooldown check, Slack notify, edr-release environment approval — but publishes with --dry-run. Real publishes happen only on push events (checked in the publish script, not a skippable job condition). This replaces the old dispatch behavior (a warm CI run, which edr-npm-ci.yml's dispatch now provides) and gives us a supported way to validate release-path changes end-to-end without cutting a release.

Behavior notes:

  • A dispatch rehearsal does ping Slack and request environment approval — that's the point ("get far into the publish"), but be aware before dispatching casually.
  • The Slack commit link now falls back to <server>/<repo>/commit/<sha> when head_commit is absent (dispatch events).

⚠️ Required status checks

PR checks move from workflow "EDR NPM release" to "EDR NPM CI", and the reusable jobs get a Build and test / prefix, e.g.:

before after
Check commit Check commit (unchanged, now from EDR NPM CI)
stable - x86_64-unknown-linux-gnu - node@24 Build and test / stable - x86_64-unknown-linux-gnu - node@24
Test bindings on Linux-x64-gnu - node@22 Build and test / Test bindings on Linux-x64-gnu - node@22
Prepare release bundle Build and test / Prepare release bundle
Review release Build and test / Review release

Anyone with admin on the repo needs to update branch-protection required checks accordingly when merging.

Validation

  • actionlint (validates the workflow_call input wiring): only the 5 pre-existing shellcheck infos, now redistributed across the split files. shellcheck clean on both composite actions. zizmor 1.26.1 clean.
  • Line-by-line diff audit of old monolith vs. the three new files: every job and step lands in exactly one place with identical semantics.
  • This PR's own CI exercises the full CI path (edr-npm-ci.ymlrelease: false).
  • The release path can only be dispatch-tested once the workflows exist on main: post-merge follow-up is a workflow_dispatch rehearsal to prove the cold build, Docker Hub pulls, and --dry-run publish end-to-end.

On PRs that touch mirror-docker-images.yml, the mirror run and the
edr-npm-release.yml docker jobs start in parallel, so the docker jobs
could race ahead and fail their pulls with 'manifest unknown', needing
a manual re-run once the mirror was green. The select-node-image action
now polls the mirror workflow run for the same head SHA (when one
exists) until it completes, same technique the HH3 regression benchmark
uses to wait for edr-ci.yml.

Closes #1593
check_commit's stringly tag output gated five different steps and jobs
with three different expression shapes; a mistake in any of them would
silently put a release build on the unsafe path (warm poisonable cache,
mirror image in the supply chain). Derive an explicit is_release output
from tag in one place, gate every consumer with the same expression,
and prove the cold-build invariant at runtime: release builds now fail
loudly if a cargo cache was restored. tag itself remains only as the
npm dist-tag for publish.

Raised by @anaPerezGhiglia in the #1587 review (#1593).
@nebasuke
nebasuke temporarily deployed to github-action-benchmark August 3, 2026 20:23 — with GitHub Actions Inactive
@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 47338ac

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@nebasuke
nebasuke temporarily deployed to github-action-benchmark August 3, 2026 20:25 — with GitHub Actions Inactive
@nebasuke
nebasuke temporarily deployed to github-action-benchmark August 3, 2026 20:25 — with GitHub Actions Inactive
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.82%. Comparing base (363d008) to head (47338ac).
⚠️ Report is 1 commits behind head on ci/mirror-wait.

Additional details and impacted files
@@                Coverage Diff                 @@
##           ci/mirror-wait    #1600      +/-   ##
==================================================
- Coverage           79.88%   79.82%   -0.07%     
==================================================
  Files                 452      453       +1     
  Lines               78956    79019      +63     
  Branches            78956    79019      +63     
==================================================
  Hits                63076    63076              
- Misses              13698    13761      +63     
  Partials             2182     2182              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The wait step could hard-fail all five docker job families on any
non-success mirror conclusion — including fork PRs, whose mirror run
skips itself by design and concludes 'skipped', and a failed weekly
re-sync sharing main's head SHA. The pull itself is the authoritative
loud failure for a genuinely missing tag ('manifest unknown'), so
warn and proceed on non-success or timeout instead of failing.

Also from review: reword the cold-build assert comment (it's a
tripwire against future edits to the cache gate, not a proof of the
is_release derivation), log the non-release decision in check_commit
instead of falling through silently, mention the wait in the action
description, and trim reviewer-facing comments.
edr-npm-release.yml did double duty as PR validation and release
pipeline, with the mode threaded through job- and step-level is_release
conditions that were easy to miss or drop. Move build/test/prepare/
review into edr-npm-build.yml (workflow_call, one typed 'release'
input), called by a thin edr-npm-ci.yml with release: false and a thin
edr-npm-release.yml with release: true. Each caller has exactly one
condition, at its entry: CI skips release pushes, and the release
workflow's jobs all hang off a single release_gate job, so release-only
jobs no longer carry guards at all. The commit-message detection moves
into a check-release-commit composite shared by both entries.

workflow_dispatch on the release workflow is now a rehearsal: it forces
the full release path (cold build, Docker Hub images, cooldown,
environment approval) but publishes with --dry-run; real publishes
happen only on push events.

Raised by @anaPerezGhiglia in the #1587 review (#1593).
@nebasuke
nebasuke force-pushed the ci/npm-release-split branch from 7afdff5 to 47338ac Compare August 5, 2026 12:33
@nebasuke
nebasuke temporarily deployed to github-action-benchmark August 5, 2026 12:33 — with GitHub Actions Inactive
@nebasuke
nebasuke temporarily deployed to github-action-benchmark August 5, 2026 12:35 — with GitHub Actions Inactive
@nebasuke
nebasuke temporarily deployed to github-action-benchmark August 5, 2026 12:35 — with GitHub Actions Inactive
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.

1 participant