Skip to content

fix(publish): keep dependency order and branch-first push in per-package mode - #765

Merged
goosewobbler merged 3 commits into
mainfrom
fix/publish-per-package-order
Oct 24, 2026
Merged

goosewobbler merged 3 commits into
mainfrom
fix/publish-per-package-order

Conversation

@goosewobbler

@goosewobbler goosewobbler commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Description

Per-package mode had two bugs. That mode runs when any update carries a tag: single, async, version.groups, or sync with packageSpecificTags.

  • Dependency order was lost.
    • Each stage got one update, so the npm and cargo dependency sorts, and every registry's publishOrder, had nothing to order. Packages published in VersionOutput order, so a dependent crate could publish before its path dependency and cargo publish would then fail.
    • Fix: before the loop, the updates are ordered with core's topologicalOrder (the function the batch sorts use), over the constraints of every selected registry at once. The constraints are npm workspace dependencies, cargo path dependencies, and a configured publishOrder, which keeps batch mode's listed-then-rest order.
    • The constraints are combined, not applied as npm then cargo then pub, because a hybrid package (package.json and Cargo.toml in one directory) publishes to both registries in one step, before its tag. Only real constraints count: npm's arbitrary order for unrelated packages would contradict cargo's.
  • The tag was pushed before the branch. If a ruleset rejected the branch push, the tag was already on the remote. The branch is now pushed first, as in batch mode.

Related Issue(s)

Refs #735. This doesn't address item 3 there (re-running failed jobs after a partial publish); it stays open in that issue.

Type of Change

  • feat — New feature
  • fix — Bug fix
  • docs — Documentation change
  • refactor — Code refactoring
  • test — Adding or updating tests
  • chore — Maintenance, dependencies, tooling
  • Breaking change — Requires major version bump

Scope

  • @releasekit/version
  • @releasekit/notes
  • @releasekit/publish
  • @releasekit/release
  • @releasekit/config
  • @releasekit/core
  • CI / workflows
  • Root / monorepo

Checklist

  • Lint passes (pnpm lint)
  • Tests pass (pnpm test)
  • Typecheck passes (pnpm typecheck)
  • Documentation updated (if applicable)
  • Breaking changes noted in description

Testing Notes

  • New tests: these call runPipeline in per-package mode with the real stages, mocking exec, git and auth.
    • A crate listed before its path dependency publishes second.
    • An npm package listed before its dependency publishes second.
    • A hybrid package between an npm dependent and a crate dependency keeps both registries' orders.
    • With two hybrids whose npm sides are unrelated, where one's crate depends on the other's, the dependency crate publishes first, whatever the input order.
    • A configured cargo publishOrder, and a configured npm one, beat input order.
    • When a ruleset rejects the branch push, no tag push is attempted.
    • All seven fail against main's @releasekit/publish source.
  • Updated: the three pushPackageTag tests that pinned tag-first now expect branch-first. The branch-rejection test also asserts that the tag isn't pushed.
  • Gate: lint, typecheck and pnpm test pass, including the root integration suite.

🤖 Generated with Claude Code

…age mode

Per-package mode gave each publish stage a single update, so the npm and
cargo dependency sorts and every registry's publishOrder had nothing to
order, and packages published in VersionOutput order. Merge each selected
registry's own publish order into one sequence before the loop.

pushPackageTag also pushed the tag before the branch, so a rejected branch
push left an orphan tag on the remote. Push the branch first, as batch mode
does.

Refs #735

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Standing release PR: #730 · v0.42.1 queued (4 packages) · open 6h 43m · ✅ ready to merge

Release Preview — v0.42.0 → v0.42.1

Note: Labels on this PR are advisory in standing-pr mode. Bumps come from conventional commits in the standing PR; override by editing labels on the standing PR itself. Add release:immediate to bypass the standing PR and release this PR directly.

These changes will be added to the release PR (#730) when merged:

Changelog

monorepo 0.42.0 → 0.42.1

Changed

  • cover a configured npm publishOrder in per-package mode (publish)
  • cover jsonOutput when disabled; fix inverted guard test (version) (PR #754 · closes #725)
  • drop versionCalculator tests that mocked a missing helper (version) (PR #755 · closes #726)
  • link standing-pr.yml comments to published ci-setup URLs (templates) (PR #756 · closes #728)

Fixed

  • report publish PipelineError partial output in --json (release) (PR #760)
  • report merge without publish as changed (release) (PR #748 · closes #724)
  • resolve relative --project-dir before loading config (version) (PR #757 · closes #698)
  • untagged-package warning matches resolved changelog range (version) (PR #749 · closes #727)
  • commit the standing PR's release branch with the PR title (release) (PR #729)
  • keep root changelog history when several packages release (notes) (PR #775 · closes #736)
  • order per-package updates by real dependencies only (publish)
  • keep dependency order and branch-first push in per-package mode (publish)

All packages → 0.42.1 (sync versioning)

  • @releasekit/version
  • @releasekit/notes
  • @releasekit/publish
  • @releasekit/release

After merge — predicted release

No version escalation — this PR's changes will be included in the queued release without affecting the projected versions.

Package Standing PR This PR After merge
monorepo 0.42.1 0.42.1 0.42.1

Updated automatically by ReleaseKit

@goosewobbler

Copy link
Copy Markdown
Owner Author

Order-dependent crate failure with two hybrids (found in a review pass; not pushed, since this PR has an owner)

orderPerPackageUpdates turns each registry's whole sorted order into dependencies: every target depends on everything before it in that order. For two hybrid packages whose npm sides are unrelated, npm's order is arbitrary (input order), so it can contradict cargo's real path dependency. The cycle then falls back to input order, and a crate publishes before its path dependency.

Repro, added to pipeline-per-package.spec.ts on 9c7f6a1:

it('should publish a hybrid crate after its path dependency when the npm sides are unrelated', async () => {
  write('packages/a/package.json', packageJson('a'));
  write('packages/a/Cargo.toml', cargoToml('a-crate', 'b-crate = { path = "../b", version = "0.1.0" }\n'));
  write('packages/b/package.json', packageJson('b'));
  write('packages/b/Cargo.toml', cargoToml('b-crate'));

  await runPipeline(
    perPackageInput([update('a', 'packages/a/package.json'), update('b', 'packages/b/package.json')]),
    getDefaultConfig(),
    options,
  );

  expect(publishedLabels().filter((l) => l.startsWith('cargo'))).toEqual([
    'cargo publish b-crate@0.2.0',
    'cargo publish a-crate@0.2.0',
  ]);
});

It fails with a-crate first. Listing b first makes it pass, so the outcome depends on VersionOutput order.

Suggested fix: build edges from real dependencies (npm workspace deps from each package.json, cargo pathDeps from the discovered CargoTargets). Use the prefix chain only for a registry with an explicit publishOrder, where the configured order is the intent.

Turning each registry's whole sorted order into constraints also encoded
npm's arbitrary order for unrelated packages. For two hybrids whose crates
depend on each other but whose npm sides don't, that contradicted cargo's
path dependency, the cycle fell back to input order, and a crate could
publish before its dependency. Constrain only by npm workspace deps, cargo
path deps, and a configured publishOrder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@goosewobbler
goosewobbler merged commit 12df5a1 into main Oct 24, 2026
32 checks passed
@goosewobbler
goosewobbler deleted the fix/publish-per-package-order branch October 24, 2026 01:19
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