Skip to content

fix(ci): publish packages in topological (dependency) order - fixes #3045#3052

Open
hbmartin wants to merge 5 commits intoresend:canaryfrom
hbmartin:topologically-ordered-publishing
Open

fix(ci): publish packages in topological (dependency) order - fixes #3045#3052
hbmartin wants to merge 5 commits intoresend:canaryfrom
hbmartin:topologically-ordered-publishing

Conversation

@hbmartin
Copy link

@hbmartin hbmartin commented Mar 11, 2026

(n.b. over half of the added lines are tests, see below for testing directions)

  • Halt publishing when a dependency fails: if any package fails to publish, all of its dependents are transitively skipped, preventing @react-email/components from referencing versions that don't exist on npm
  • Add --dry-run flag to scripts/release.mts: builds all packages, checks the npm registry, and prints the topological publish plan with build status without actually publishing
  • Add unit tests for the publish pipeline

Motivation

@react-email/components re-exports all 18+ component packages as direct dependencies. If any one of those packages fails during npm publish, changeset publish continues publishing the rest. There is no built-in way to make changeset publish bail on failure or respect dependency order. These leads to errors such as #3041 and #3047

Implementation

scripts/release-utils.mts implements the publish pipeline:

  1. Determine what to publish: each non-private workspace package is checked against the npm registry via npm view <pkg>@<version>
  2. Build a dependency graph scoped to the publish set: using dependencies and devDependencies from each package.json (via @manypkg/get-packages, already a dev dependency)
  3. Topological sort (Kahn's algorithm): leaf packages with no workspace dependencies in the publish set are published first
  4. Publish with failure tracking: a failed set tracks any package that errors. Before publishing each package, its workspace dependencies are checked against the failed set; if any dependency failed, the package is skipped and added to the failed set transitively
  5. Exit with non-zero code if any packages failed, failing the workflow

scripts/release.mts changes are intentionally minimal i.e. getChangelogEntry, createRelease, and the git tag/release loop are unchanged. The middle section replaces the single pnpm release call (which ran turbo build && changeset publish as a black box) with explicit build + topologicalPublish().

Testing

  • The dry run command will show publishing order and status: node --import tsx/esm ./scripts/release.mts --dry-run
  • The pipeline test can be run as: pnpm vitest run scripts/release-utils.test.mts

Summary by cubic

Publishes workspace packages in topological dependency order (including required peer deps) and skips dependents if any publish fails. Adds a --dry-run mode to preview the plan with dist-tags, build status, and dependency info, and hardens registry checks and prerelease tag selection.

  • New Features

    • Ordered publish that skips private and already-published packages; ordering uses runtime deps only (dependencies + optionalDependencies + required peerDependencies).
    • Explicit build before publish; --dry-run shows order, per-package dist-tag, build status, and dependencies, and continues even if build failed.
    • Per-package prerelease behavior: packages with only current prereleases publish to latest; brand-new prerelease packages use the prerelease tag.
  • Bug Fixes

    • Dependents are skipped when a dependency publish fails; workflow exits non-zero on any failure.
    • Registry probe checks published versions and throws on non-404 errors to avoid false “unpublished” reads.
    • Prevents broken @react-email/components releases by enforcing dependency order.

Written for commit 97bc996. Summary will update on new commits.

…ing to pnpm changeset publish

- Halt publishing when a dependency fails: if any package fails to publish, all of its dependents are transitively skipped, preventing @react-email/components from referencing versions that don't exist on npm
- Add --dry-run flag to scripts/release.mts: builds all packages, checks the npm registry, and prints the topological publish plan with build status without actually publishing
- Add unit tests for the publish pipeline

### Motivation

`@react-email/components` re-exports all 18+ component packages as direct dependencies. If any one of those packages fails during `npm publish`, `changeset publish` continues publishing the rest. There is no built-in way to make changeset publish bail on failure or respect dependency order. These leads to errors such as resend#3041 and resend#3047

### Implementation

`scripts/release-utils.mts` implements the publish pipeline:

1. Determine what to publish: each non-private workspace package is checked against the npm registry via npm view <pkg>@<version>
2. Build a dependency graph scoped to the publish set: using dependencies and devDependencies from each package.json (via @manypkg/get-packages, already a dev dependency)
3. Topological sort (Kahn's algorithm): leaf packages with no workspace dependencies in the publish set are published first
4. Publish with failure tracking: a failed set tracks any package that errors. Before publishing each package, its workspace dependencies are checked against the failed set; if any dependency failed, the package is skipped and added to the failed set transitively
5. Exit with non-zero code if any packages failed, failing the workflow

`scripts/release.mts` changes are intentionally minimal i.e. getChangelogEntry, createRelease, and the git tag/release loop are unchanged. The middle section replaces the single pnpm release call (which ran `turbo build && changeset publish` as a black box) with explicit build + `topologicalPublish()`.

### Testing

- The dry run command will show publishing order and status: `node --import tsx/esm ./scripts/release.mts --dry-run`
- The pipeline test can be run as: `pnpm vitest run scripts/release-utils.test.mts`
@vercel
Copy link
Contributor

vercel bot commented Mar 11, 2026

@hbmartin is attempting to deploy a commit to the resend Team on Vercel.

A member of the Team first needs to authorize it.

@socket-security
Copy link

socket-security bot commented Mar 11, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​nodemailer@​6.4.231001007793100
Added@​plunk/​node@​3.0.3841009179100
Added@​sendgrid/​mail@​7.7.010010010081100
Addedmailersend@​2.6.09910010083100
Addedpostmark@​3.11.01001008687100
Addednodemailer@​7.0.13971009793100
Added@​aws-sdk/​client-ses@​3.1007.09810010098100

View full report

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

⚠️ No Changeset found

Latest commit: 97bc996

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

@hbmartin hbmartin changed the title Publish packages in topological (dependency) order - fixes #3045 fix(ci): Publish packages in topological (dependency) order - fixes #3045 Mar 11, 2026
@hbmartin hbmartin changed the title fix(ci): Publish packages in topological (dependency) order - fixes #3045 fix(ci): publish packages in topological (dependency) order - fixes #3045 Mar 11, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

…04 npm errors instead of treating them as “unpublished”, publish ordering now uses runtime deps only (dependencies plus optionalDependencies), and prerelease publishing now restores the per-package Changesets fallback so only-pre packages publish to latest while brand-new prerelease packages stay on the prerelease tag.
@@ -0,0 +1,432 @@
import fs from 'node:fs/promises';
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather this be in release.mts

@gabrielmfern gabrielmfern self-requested a review March 18, 2026 20:05
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.

2 participants