Skip to content

refactor(release): one command releases every affected artifact#7

Merged
Ripwords merged 2 commits into
mainfrom
chore/unify-release-tooling
Jul 17, 2026
Merged

refactor(release): one command releases every affected artifact#7
Ripwords merged 2 commits into
mainfrom
chore/unify-release-tooling

Conversation

@Ripwords

Copy link
Copy Markdown
Owner

Tonight's CSP fix took three commands, three pushes and three full CI cycles to ship — and one of them silently failed. This makes it one command, and fixes each thing that actually went wrong along the way.

Before / after

bun run release:extension && git push --follow-tags   # tag silently not pushed
# wait ~4min for CI on the new HEAD
bun run release:sdk && git push --follow-tags         # same
# wait ~4min again
bun run release                                        # self-pushes, surprise

bun run release

→ affected since last tags (patch):
    sdk        0.4.2 → 0.4.3 → sdk-v0.4.3       → npm (@reprojs/core)
               (own changes, since sdk-v0.4.2)
    dashboard  0.6.5 → 0.6.6 → v0.6.6           → Docker Hub image
    extension  0.1.4 → 0.1.5 → extension-v0.1.5 → Chrome Web Store (public review)
               (bundles @reprojs/core, since extension-v0.1.4)

  Commit, tag and push all of the above? [y/N]

Four near-identical scripts (422 lines) → one script + a registry. One commit, one CI gate, one prompt.

Affected detection

Each artifact declares what it's built from, bundled workspace packages included:

Artifact Tag Built from
sdk sdk-v packages/{core,ui,sdk-utils,shared,recorder}
dashboard v apps/dashboard, packages/integrations + bundles the SDK
extension extension-v apps/extension + bundles the SDK
expo expo-v packages/expo, packages/{sdk-utils,shared}

Propagation then needs no special rule — the dashboard bakes the SDK IIFE into its image and the extension syncs it via sync-sdk.ts, so listing the SDK as a source is what makes a packages/ui fix reach all three. That was the fact I had to derive by hand by reading the service worker. Expo correctly stays out: it ships its own React Native UI and never bundles packages/ui.

Version lines stay independent, so a dashboard change still can't churn a republish of @reprojs/core.

Bugs fixed (each one bit for real tonight)

  • Tags are annotated. amend_release_commit_and_retag() re-tagged with git tag -flightweight — while every script printed git push --follow-tags, which carries annotated tags only. extension-v0.1.4 never reached the remote, its publish workflow never fired, and nothing errored. It needed a manual git push origin <tag> to recover.
  • Detection ignores release commits. A release bumps its own package.json/CHANGELOG.md inside its own paths, so a plain git diff counts chore(release): sdk-v0.4.2 as an SDK change and marks everything bundling core as affected — every release making the next one look necessary, forever.
  • Changelog history stopped being deleted. Separate commit, pre-existing, and the nastiest one: the filter ran over the whole file with a keep-set covering only FROM..HEAD, so every past entry's bullets were dropped. packages/core/CHANGELOG.md's sdk-v0.4.0 entry went 257 bullets → 2; sdk-v0.3.0 107 → 1. Headings survived, so the file looked fine while draining. That gutted file ships in the npm tarball and gets pasted into each GitHub Release.
  • Push happens in one place. "postrelease": "git push --follow-tags" fired implicitly off bun run release, so the dashboard auto-pushed while its own script printed "Next: push". Both implicit hooks are gone; the script runs its own verification.
  • Tags push one at a time. GitHub won't start workflow runs for tags beyond the third in a single push, and there are four artifacts.
  • release:all deleted. It chained scripts with no push between, so the second always died on no CI run found for <sha>.

Verification

Tested in a throwaway clone against a throwaway bare origin, so nothing could reach the real remote:

Test Result
No changes since tags only expo — correct, packages/shared really has changed since expo-v0.3.0
Synthetic packages/ui change sdk + dashboard + extension, extension reason = bundles @reprojs/core
Dashboard-only change dashboard only — no over-propagation into sdk/extension ✅
--only / --skip / --minor all correct; --minor gives a real 0.6.5 → 0.7.0, not silently downgraded on 0.x
Full run exactly 1 commit; git cat-file -t v0.6.6tag (annotated); tag annotated on the remote too
Changelog 1094 → 1112 lines (+18, history preserved); diffstat 19 insertions, 1 deletion vs 490 deletions before the fix

A push rejection mid-test also confirmed it fails loudly rather than silently.

Note

expo has genuinely unreleased packages/shared changes since expo-v0.3.0 — the tool surfaced that on its first run. Not addressed here; worth deciding separately whether to cut expo-v0.3.1.

The already-gutted changelogs are recoverable from old tags (e.g. git show sdk-v0.4.0:packages/core/CHANGELOG.md still has all 257 bullets). Not done here — happy to follow up if you want the history restored.

🤖 Generated with Claude Code

Ripwords and others added 2 commits July 17, 2026 12:28
Four near-identical scripts (422 lines) became one. Releasing a single
packages/ui bugfix meant three commands, three pushes and three full CI
cycles — each release commit moved HEAD, and every script re-gated on green
CI for the new HEAD — plus knowing by heart that the dashboard and extension
both bundle the SDK and so had to go too.

`bun run release` now works out what's affected, shows the plan, asks once,
and does the lot in one commit with one CI gate:

    → affected since last tags (patch):
        sdk        0.4.1 → 0.4.2 → sdk-v0.4.2       → npm
        dashboard  0.6.4 → 0.6.5 → v0.6.5           → Docker Hub
        extension  0.1.3 → 0.1.4 → extension-v0.1.4 → Web Store

Affected is derived from what each artifact is BUILT FROM, bundled workspace
packages included. Propagation then needs no special rule: the dashboard bakes
the SDK IIFE into its image and the extension syncs it via sync-sdk.ts, so
listing the SDK as a source is what makes a packages/ui fix reach all three —
and expo, which ships its own React Native UI, correctly stays out of it.

Fixes found while doing this, each having already caused a real failure:

- Tags are annotated. amend_release_commit_and_retag() re-tagged with
  `git tag -f` (lightweight), while every script printed `git push
  --follow-tags` as the next step — which pushes annotated tags only. The tag
  silently never reached the remote and the publish workflow never fired.
  extension-v0.1.4 hit exactly this and needed a manual `git push origin
  <tag>` to recover.
- Detection ignores release commits. A release bumps its own package.json and
  CHANGELOG.md inside its own paths, so a plain `git diff` counts
  "chore(release): sdk-v0.4.2" as an SDK change and marks everything that
  bundles core affected — every release making the next one look necessary.
- Push happens in one place. "postrelease": "git push --follow-tags" fired
  implicitly off `bun run release`, so the dashboard auto-pushed while its own
  script printed "Next: push" and the other three genuinely didn't. Both
  implicit hooks are gone; the script does its own verification.
- Tags push one at a time: GitHub won't start workflow runs for tags beyond
  the third in a single push, and there are four artifacts.
- release:all is deleted. It chained scripts with no push between them, so the
  second always died on `no CI run found for <sha>`.

Version lines stay independent, so a dashboard change still can't churn a
republish of @reprojs/core.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
filter_changelog_by_paths ran over the WHOLE CHANGELOG.md, dropping every line
whose [sha] wasn't in the keep-set. The keep-set only covers FROM..HEAD, so
every previously-released entry's bullets referenced SHAs outside the range
and were deleted — each release eroding a bit more of the file.

packages/core/CHANGELOG.md, entry by entry, as of sdk-v0.4.0 vs now:

    sdk-v0.4.0   257 bullets → 2
    sdk-v0.3.0   107 bullets → 1
    sdk-v0.2.1     4 bullets → 1

The headings survived (no [sha] to match), so the file kept looking plausible
while its contents drained away. That gutted file is what ships in the npm
tarball and gets pasted into each GitHub Release.

Only filter the section changelogen just prepended; copy everything from the
second `## ` heading down through untouched. `## ` matches version headings
only — `### Fixes` has no space in the third column.

This is pre-existing and independent of the release-script unification; it
just took releasing from a clone and diffing the result to notice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ripwords
Ripwords merged commit 61ea2d6 into main Jul 17, 2026
8 checks passed
@Ripwords
Ripwords deleted the chore/unify-release-tooling branch July 17, 2026 04:41
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