feat(scripts): detect modified-but-unreleased dependencies during release#436
Open
sandersaares wants to merge 96 commits into
Open
feat(scripts): detect modified-but-unreleased dependencies during release#436sandersaares wants to merge 96 commits into
sandersaares wants to merge 96 commits into
Conversation
…lease When releasing a crate, an author may have also modified one of its upstream workspace dependencies but forgotten to release the dependency too. Locally everything builds via path-references, but once published the released crate resolves to the last released version on crates.io, missing the new changes. This adds two layers of automation: 1. Interactive layer (release-crate.ps1): After the existing downstream cascade finishes, scan the release set for transitive workspace dependencies that have file changes vs the PR base ref but are not themselves being released. Prompt the author (y/N + bump kind) for each finding so material changes get an extra release queued, while immaterial changes (formatting, doc tweaks) can be declined. 2. CI layer (scripts/check-unreleased-dependencies.ps1 + .github/workflows/main.yml release-deps job): Runs the same analysis non-interactively and posts a sticky PR comment listing any findings so reviewers can sanity-check materiality decisions. Shared logic lives in the new scripts/lib/releasing.ps1 library, dot-sourced by both entry-point scripts. Workspace dependency types kind=normal and kind=build are tracked; kind=dev is excluded (cannot affect downstream consumers via crates.io). Cascade re-bump is idempotent: re-cascading into a crate already at a sufficient version appends a maintenance bullet to its existing changelog section instead of double-bumping. The .delta.toml Cargo.toml trip-wire is documented as a dependency of the release-deps CI gate so any version bump touches Cargo.toml -> skip=false. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate the three remaining Invoke-GitCommand callsites in release-crate.ps1 (tag --list, git log, remote get-url) to the array-argument Invoke-Git wrapper in releasing.ps1, then delete the legacy wrapper. Removes the last use of Invoke-Expression-based git invocation in the release tooling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #436 +/- ##
=======================================
Coverage 100.0% 100.0%
=======================================
Files 335 335
Lines 25586 25586
=======================================
Hits 25586 25586 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ralfbiedert
approved these changes
May 21, 2026
Collaborator
|
Whops, I missed the |
The release-deps job previously depended on the delta job and used `delta.outputs.skip != 'true'` as a compute optimization to skip the analysis when no crate was affected. The optimization was structurally fragile - it relied on the implicit invariant that Cargo.toml and scripts/* remain in .delta.toml's trip_wire_patterns - and only saved a few seconds when there are no findings. Drop both the needs: [delta] dependency and the delta.skip gate. The check-unreleased-dependencies.ps1 script already exits fast (no findings -> no markdown, no comment) when there is nothing to report, so always running it is safe and simple. Also remove the corresponding NOTE in .delta.toml since that dependency no longer exists. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR strengthens the workspace crate release tooling by detecting “modified-but-unreleased” upstream workspace dependencies that could be missed when publishing to crates.io, and surfaces the same signal in CI via a new informational job.
Changes:
- Extracts shared release/dependency-graph and git helper logic into a dot-sourced PowerShell library (
scripts/lib/releasing.ps1). - Extends
scripts/release-crate.ps1to (optionally) scan for modified-but-unreleased upstream workspace deps after the existing downstream cascade, with interactive prompting to release them. - Adds a CI-only analyzer script and a new workflow job that posts/removes a sticky PR comment when such unreleased upstream dependency changes are detected.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| scripts/release-crate.ps1 | Adds base-ref/non-interactive options, refactors cascade flow, and performs a post-release upstream dependency scan. |
| scripts/lib/releasing.ps1 | New shared library providing safe git invocation, SemVer helpers, workspace metadata, and unreleased-dependency analysis. |
| scripts/check-unreleased-dependencies.ps1 | New CI companion script that emits a markdown report + step output for unreleased upstream dependency changes. |
| .github/workflows/main.yml | Adds release-deps job to run the CI analyzer and post/remove a sticky PR comment. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- release-crate.ps1: drop -AllowFailure on the fetch in the base-ref resolver so the surrounding try/catch can actually trigger and emit a warning on fetch failure (previously the catch was unreachable because -AllowFailure returns $null instead of throwing). - check-unreleased-dependencies.ps1: route Get-RepoRoot through Invoke-Git instead of shelling out directly, matching the design described in the PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
martintmk
approved these changes
May 21, 2026
…rades them When the post-release dep scan runs a nested `Invoke-ReleaseFlow` for an upstream crate the user opts to release, the resulting cascade may upgrade a crate that was already in the release set (e.g., the initial release patch-bumped `foo` and the nested major release of an upstream dep now requires a major on `foo`). Previously the merge skipped duplicates outright, so `Show-ReleaseSummary` and the final `feat(crate): release v<version>` message reported a stale version that did not match what was actually written to `Cargo.toml`. Replace the skip-on-duplicate logic with an in-place update: keep the original `OldVersion` (the pre-PR baseline) and adopt the latest `NewVersion` from the nested cascade. New crates are still appended as before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous logic compared the working tree against the PR base ref to decide which workspace crates had unreleased modifications. That missed a real scenario: an earlier PR merges a source change to `bytesbuf` without bumping its version, and a later PR bumps `bytesbuf_io` (which depends on `bytesbuf`). On crates.io the published `bytesbuf_io` resolves to the last released `bytesbuf`, which does *not* include the unreleased modification. Because the modification predates the PR's base ref, the old `BaseRef`- relative scan saw nothing to flag. Switch each crate's "modification baseline" to its own most-recent commit that touched `version =` or `publish =` in its `Cargo.toml`, derived via `git log -1 -G '^(version|publish)\s*='`. Any change under `crates/<folder>/` newer than that commit (committed, working-tree, or untracked) is treated as unreleased. `Get-CratesWithVersionBumps` (release-set detection) intentionally still diffs against the PR base ref — that's the correct anchor for "what is this PR releasing". Replaces `Get-GitFileChangeSet` / `Get-CratesWithFileChanges` with the new `Get-CrateLastReleaseBaseline` + `Get-CratesWithUnreleasedChanges` helpers and rewrites `Get-UnreleasedModifiedDependencies` to consume the per-crate modification map. Also persists the manual test plan as `scripts/RELEASE-DEPS-TEST-CASES.md` so future agents can re-run T1-T16 (original PR-vs-base coverage) and N1-N10 (multi-PR baseline coverage) when the logic changes. All N1-N9 scenarios were verified in a scratch worktree before this commit; N10 (brand-new crate) is structurally covered by the new-crate branch in `Get-CratesWithVersionBumps`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two real defects flagged on the latest review pass:
1. `check-unreleased-dependencies.ps1` was supposed to alphabetically sort
the release-set listing in the sticky PR comment, but the chain
`@(Get-CratesWithVersionBumps ...) | Sort-Object` silently broke. The
helper returns its HashSet via `Write-Output -NoEnumerate` so callers
can use `.Contains()`. That wrapping makes `Sort-Object` receive a
single object (the HashSet itself), so the sort is a no-op and the
foreach below iterates the HashSet in insertion order. Fixed by
unwrapping with `... | ForEach-Object { $_ }` before sorting.
2. `Add-CascadeBulletToVersionSection`'s `if ($subStart -ge 0)` branch
built the new file content with
`@($lines[0..($insertAt - 1)]) + @($bullet) + @($lines[$insertAt..($lines.Count - 1)])`.
When `$insertAt` equals `$lines.Count` (target sub-header is the last
content in the file, no bullets yet, no trailing blank lines), the
right-hand slice becomes `$lines[N..N-1]` which is a reverse-range that
silently aliases to the last element — so the last line was duplicated.
Reproduced on a synthesised changelog before fixing. Mirrored the EOF
guard that the `else` branch already has.
Verified by direct PowerShell repros for both: sort now yields
alphabetical order, EOF insertion no longer duplicates the sub-header
line, and the non-EOF + idempotency paths are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Anticipates a future home for other script-related test material. The doc is unchanged; only its location moves. References inside the doc point at other scripts (`scripts/lib/releasing.ps1`, `scripts/release-crate.ps1`, `scripts/check-unreleased-dependencies.ps1`) from the repo root and remain valid after the move. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e set Documentation drift after the per-crate baseline refactor (cedd750). Four Copilot review comments on PR #436 flagged that synopses, parameter docs, and the user-facing PR-comment / interactive-warning text still said "modified vs the PR base ref" — but only the release-set anchor uses BaseRef; modifications are evaluated per crate against each crate's own last `version =` / `publish =` commit. Updated: - `scripts/check-unreleased-dependencies.ps1` — synopsis, description, BaseRef parameter docs, and the markdown body line ("unreleased modifications — changes newer than their last `version =` or `publish =` bump"). - `scripts/release-crate.ps1` — BaseRef parameter comment, the `Invoke-PostReleaseDepScan` function docstring, and the interactive warning text. (Switched the warning from a `"..."` to a `'...'` literal so the inline `` `version =` `` / `` `publish =` `` backticks aren't interpreted as PowerShell escape sequences — `` `v `` is vertical tab.) - `.github/workflows/main.yml` — `release-deps` job comment. Self-reviewed the rest of the PR diff for similar drift; the remaining "vs base" mentions (release-set BFS docstring, "Fetch base ref" workflow step name, "What this means" sticky-comment block) are legitimate and unchanged. Re-ran N1-N9 against the updated scripts in a scratch worktree: 9/9 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the non-obvious lessons learned while building the N-series and T-series test harnesses, so future maintainers/agents who need to rebuild a harness from scratch don't have to relearn them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- release-flow.ps1: Set-Content on package + root Cargo.toml now uses -LiteralPath and -Encoding utf8, matching the surrounding changelog writes and guaranteeing deterministic UTF-8 output. - Scenarios.Tests.ps1: hoisted the scenario discovery to top-level so the cached list is the single source of truth used by both the BeforeAll wiring and the It -ForEach generator (Pester 5 evaluates -ForEach at Discovery, BeforeAll at Run, so the top-level body is the only place both phases can share state). - GitFs.Tests.ps1: fixed 8 mis-indented \ assignments inside per-Describe BeforeAll blocks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ith -Changed/-All modes
release-packages.ps1 becomes a parameter-set CLI shell exposing three mutually-exclusive target-selection modes:
- -Packages '<name>@<change-spec>', ... (existing targeted mode; the only non-interactive mode).
- -Changed (interactive guided walk through every workspace package with on-disk modifications; merged in from release-changed-packages.ps1).
- -All (interactive guided walk through every publishable workspace package, even ones with no detected changes; new mode for coordinated multi-package release planning).
release-changed-packages.ps1 is deleted (no compatibility shim — the unified entry point IS the canonical interface).
Invoke-ReleaseChangedPackagesMain is folded into Invoke-ReleasePackagesMain with a -Mode parameter ('targeted'|'changed'|'all'). -All mode synthesises a private modified-snapshot — adding ChangedFileCount=0 stubs for every published package not already present — so Get-UnreleasedModifiedDependencies's surfacing predicate accepts every published package without threading a new mode flag through the planner. The synthetic snapshot is scoped to this entry point so its zero-count entries never escape into PR-comment / dep-scan reporting contexts.
Format-PackageMenu adapts to ChangedFileCount: when zero (or the property is missing) the header switches to 'Reviewing package (no detected changes): <folder>' and slot 1 is relabelled 'View diff (no changes in this package)'. The View-diff option stays in slot 1 in both flavours so muscle memory works.
Scenarios S19/S20/S21 are renamed (dropping the all- prefix) and their Mode field migrated to 'changed'. New scenarios S22 (-All ignore-everything walk) and S23 (-All force-release with cascade-breaking propagation) lock down the new mode. Four new PromptFlow unit tests cover the header/label switch on missing-or-zero ChangedFileCount.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…/-Changed/-All modes The top-of-file script list now describes the single release-packages.ps1 with all three target-selection modes. How to release one or more packages now shows three command examples (one per mode) and an updated step list that reflects the unified pipeline. The previous 'Guided changed-packages workflow' section is rewritten as 'Guided modes (-Changed, -All)' covering both interactive modes, the View-diff label rewrite in -All, the elevation-suppression behaviour, the BFS-root model, and the interactive-only restriction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…chain printout)
The per-package review menu rendered every full in-workspace dependency chain ending at the package under review, one per line. In a workspace with hundreds of packages this multi-line printout was visually overwhelming and rarely told the reviewer anything actionable — what they actually need at decision time is the set of direct dependents (those are what cascade-toward-dependents pivots on).
Replace the multi-line chain block with a single comma-separated line:
Direct dependents in this workspace: bytesbuf, http_extensions
The set is derived from WorkspaceDependencyChains (chain[-2] of every chain, deduplicated and sorted ordinal) so it stays release-set-independent. The empty-set branch ('no in-workspace dependents') is preserved unchanged.
Updated PromptFlow unit tests to assert the new format (header line count, dedup across chains, transitive root is filtered out, scoped no-'->' check on the dependents line only since sibling version-transition hints legitimately contain '->').
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… SemVer 2.0 + MINIMUMS footer Address reviewer feedback for round 5: - Remove all 'non-interactive' framing from release-packages.ps1 help text and docs/releasing.md. Every mode (Packages/Changed/All) is interactive; the script is intended for interactive use by maintainers. Test-InteractiveSession is now an unconditional gate. - Accept SemVer 2.0 pre-release and build-metadata suffixes in -Packages explicit version pins (e.g. foo@1.2.3-pre01, bar@1.0.0+meta). Strictly reject 1- or 2-component versions and leading-zero numeric identifiers. New Split-SemanticVersion helper drives all version math (Get-NextVersion strips pre-release on increment; Compare-SemanticVersions validates then casts to [semver] for SemVer-2.0 ordering). - Add MINIMUMS-semantics footer to Show-ReleasePlan: clarifies that user-requested change types are minimums subject to cascade auto-upgrade, except explicit version pins which are not auto-upgraded (the planner errors out if a cascade requires more). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e edits as baseline-resetting Get-PackageLastReleaseBaseline now matches both literal `version =` / `publish =` keys AND the TOML inheritance form `version.workspace =` / `publish.workspace =` (which inherit from the workspace root). Previously, packages that switched their publish or version key from a literal to the dotted-key inheritance form (or vice versa) would not have their baseline reset, leaving stale change-detection results. The regex is a POSIX ERE because git's -G flag does not accept PCRE extensions like (?:...) — capturing groups are used instead. Synthetic-workspace fixture extended with a PublishSyntax option (omitted / literal-true / literal-false / dotted-workspace / inline-workspace) and an optional [workspace.package] block, so tests can exercise every TOML shape. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pin-vs-cascade rejection By default, Resolve-ReleaseSet rejects the release plan when an explicit version pin (e.g. `bar@1.1.0`) numerically undershoots what cascade analysis requires (e.g. cascade requires v2.0.0). The rejection forces the user to either revise the pin or pick a change-type keyword, which guarantees that an explicit pin is honored verbatim or the script refuses to proceed. -Force relaxes this specific rejection: the explicit pin is honored verbatim, the package's EffectiveChangeType tag is upgraded so any further cascade decisions for the package are correct, and a warning is printed flagging that downstream consumers may break. The resolved entry carries a new `PinHonoredAgainstCascade` flag so Show-ReleasePlan can warn the user clearly (per-entry tag plus footer line in yellow). -Force does NOT relax the always-fatal `pin not strictly greater than current on-disk version` check, and has no effect on change-type tokens (which the planner always auto-upgrades silently). The default-mode rejection error message now mentions -Force so the user knows about the override. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-All) -Changed and -All only accept change-type answers (breaking / non-breaking / patch) from the per-package prompt — they never accept explicit version pins. The pin-vs-cascade rejection that -Force overrides therefore cannot fire in those modes, so the switch is meaningless there. Bind -Force to the 'ByPackages' parameter set so PowerShell rejects `-Changed -Force` / `-All -Force` at parameter resolution time with a clear `Parameter set cannot be resolved` error. Also add a defensive guard in Invoke-ReleasePackagesMain for callers that invoke the library function directly, with a clearer human-readable error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ding with 'Skip package'
The previous wording implied a single motivation (immateriality of the changes) for not releasing a surfaced package, but there are many other reasons a reviewer may pick this option in any given iteration (e.g. wanting to release the package separately, deferring the decision, etc.). 'Skip' is also a better fit semantically — it conveys that the decision is for the current iteration, not a permanent verdict; cascade may still pull the package into the plan on a later iteration.
Updates the per-package post-skip console line for the same reason: drops the 'reviewer should confirm the change is immaterial' framing in favor of 'cascade may still pull it into the release plan on a later iteration', which describes the actual mechanics.
Internal action vocabulary ('ignore') is unchanged — that is implementation detail and renaming it would churn many tests/scenarios without user-visible benefit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… consistency Other lines in the per-package menu start with a capital letter (e.g. 'Direct dependents in this workspace: ...', 'View diff', 'Skip package', 'Release as ...'). Capitalise this one too. Also touch up the docs/releasing.md reference and replace a leftover 'ignore every prompt' phrasing with 'skip every prompt' to match the new menu wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… of run Show-ReleaseSummary previously rendered the entries in the topological order Invoke-ResolvedRelease processed them in (dependencies first, dependents last), which is meaningful while releases are being executed but is hard to scan as a final summary. Sort alphabetically by package name so users can quickly locate any specific package. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two threads of polish for PR #436. ## Round-5 reviewer comments (Copilot bot) * `scripts/lib/check-unreleased-deps.ps1`: - `Set-StepOutput`: switch `Add-Content` to `-LiteralPath` so wildcard metacharacters in `$env:GITHUB_OUTPUT` cannot trigger glob expansion. Pin `-Encoding utf8` explicitly so the file format is deterministic across PowerShell versions (Windows PowerShell 5.1 defaults to UTF-8 with BOM otherwise). - `Invoke-CheckUnreleasedDependencies`: same `-LiteralPath` switch for the `Set-Content` that writes the comment body. * `.github/workflows/main.yml`: - Merge `origin/main` so the workflow gets the dependabot bump `actions/checkout` v6.0.2 -> v6.0.3 (#470). Bump the two newly-introduced jobs (`release-deps`, `script-tests`) to v6.0.3 too so the file is consistent. - Replace the `release-deps` job comment's "workspace crates" / "the crate's own last `version =`" language with "workspace packages" / "the package's own last `version =`" for terminology consistency with the rest of the release tooling. * `scripts/mutants.rs`: - Replace the `{s}` suffix-trick that produced "as individual group" in the singular case with a proper if-branched phrase: "Added 1 ungrouped package as an individual group" vs "Added N ungrouped packages as individual groups". ## Pluralisation pass PR-wide audit for "coward pluralisation" (printing plural-only forms even when the count is 1). Every user-facing string now picks the correct singular or plural form based on the count it is reporting. * `Format-PackageMenu` (per-package review menu): - "Direct dependents in this workspace: foo" -> "Direct dependent ..." when exactly one direct dependent is listed. * `Show-ReleasePlan` (cascade-upgrade footer): - "Items above tagged 'auto-upgraded by cascade' were upgraded ..." -> "Item above tagged ... was upgraded ..." when exactly one entry is auto-upgraded. - "Items above tagged '-Force: pin honored over cascade' kept their explicit version pin ..." -> "Item above tagged ... kept its explicit version pin ..." when exactly one entry is forced. * `Format-UnreleasedDependenciesReport` (PR comment markdown): - "This PR releases the following workspace packages:" -> "... workspace package:" when exactly one is being released. - "The following workspace packages have **unreleased modifications** (changes newer than their last ...) and are *not* part of this release:" -> "... package has ... its last ... and is *not* part ..." for one finding. - "The following workspace packages **are** part of this release, but their change type is ... while they also contain ..." -> "... package **is** part ... but its change type ... while it also contains ..." for one finding. - "Locally, the released packages build ... they will resolve ..." -> "Locally, the released package builds ... it will resolve ..." for a single-package release set. * `Invoke-CheckUnreleasedDependencies`: - "Wrote N finding(s) to ..." -> proper "Wrote N finding" vs "Wrote N findings" branch (refactor of the existing inline if-suffix trick to a noun variable). ## Tests * `PromptFlow.Tests.ps1`: - Update the existing "deduplicates direct dependents" test (which leaves exactly one distinct dependent) to assert the singular "Direct dependent" label. - Update the two existing tests with one-chain `WorkspaceDependencyChains` fixtures (formerly asserting plural "Direct dependents: a" / "Direct dependents: b") to assert singular. - Add three new tests covering the explicit singular and plural cases of the menu label (`,@(...)` trick to keep PowerShell from flattening a single-chain input). - Add four new tests in the cascade-upgrade footer Describe block for the singular vs plural wording of the two footer lines. * `CheckUnreleasedDeps.Tests.ps1`: - Add a "uses singular release-set header and footer" test and a "uses plural release-set header and footer" test for `Format-UnreleasedDependenciesReport`. - Add singular/plural intro tests for the NotReleasedFindings and ElevationCandidates tables. Total Pester count: 388 passed / 1 skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The change detection that powers -Changed (and the snapshot used for elevation review under -Packages) is scoped to files under `crates/<package>/`. Modifications elsewhere in the repository — the workspace-level Cargo.toml, .cargo/, deny.toml, shared CI workflows, top-level scripts — are invisible to the scan even when they affect how a package builds or behaves. Document this in three places so the limitation cannot be missed: * `scripts/release-packages.ps1` — Description block for -Changed mode and the `.PARAMETER Changed` help entry. * `docs/releasing.md` — both the top-of-document `-Changed` bullet and the dedicated "Guided modes" section. Each note cross-references `-All` (which surfaces every publishable package regardless of detected changes) and `-Packages` (which lets the user list affected packages explicitly) as the two correct mitigations for cross-cutting changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines
+23
to
+28
| # Appends "$Name=$Value" to $env:GITHUB_OUTPUT, when defined. A no-op outside | ||
| # GitHub Actions so local invocations don't blow up. -LiteralPath bypasses | ||
| # wildcard interpretation in case $env:GITHUB_OUTPUT contains characters PowerShell | ||
| # would otherwise treat as glob metacharacters; -Encoding utf8 is pinned explicitly so | ||
| # the file format is deterministic across PowerShell versions (Windows PowerShell 5.1 | ||
| # defaults to UTF-8 with BOM, which GitHub Actions accepts but is needlessly fragile). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When releasing a workspace package, an author may have also modified one of its workspace dependencies but forgotten to release that dependency. Locally everything builds via path-references, but once published the released package resolves against the last released version of each dependency on crates.io — missing the new changes. The dependency-side modification may even have landed in a previous PR that merged to main without a version increment, so a plain "diff vs PR base" check is not enough to catch it.
What changes for the user
Dependency review during release
release-packages.ps1now scans the dependency graph of every package in the release plan and surfaces every workspace dependency with unreleased modifications — evaluated per package against that dependency's own lastversion =/publish =commit, not against the PR base. For each finding the user can view the diff, skip the package, or release it alongside the original target.The same analysis runs on every PR via a new
release-depsGitHub Actions job, which posts a sticky PR comment listing any unreleased-but-modified workspace dependencies of the packages being released. Reviewers can sanity-check the author's materiality decisions even when the script was never run interactively.Multi-package releases are now atomic
Previously the script handled one package per invocation, with cascade decisions surfaced incrementally across re-runs. The new driver takes the entire release plan up-front:
After plan review, all writes —
Cargo.tomlversion increments,CHANGELOG.mdentries,README.mdregeneration, workspace[workspace.dependencies]updates — happen in one shot, followed by a workspacecargo check. Two guided alternatives are available when you don't have the full list ready up-front:-Changed(walk every package with unreleased modifications) and-All(walk every publishable package, modified or not).Semantic change type instead of numeric version components
The previous
-Bump major|minor|patchCLI conflated numeric version-string components with semantic intent — and they are not interchangeable: a breaking change on a0.xpackage moves the minor component, on a>=1.xpackage the major component. The new change-spec is a semantic label per package:breaking,nonbreaking, orpatch. Users who want to pin an exact target can do so explicitly —foo@1.0.0,bar@1.0.0-rc.1— with full SemVer 2.0 (three numeric components plus optional pre-release / build metadata) accepted.If the cascade computes a stronger change type than the user requested (e.g. the user said
patchbut the dependent exposes a breaking-changed dependency in its public API), the change type is auto-upgraded and the upgrade is shown in the plan display. Explicit version pins are not silently overridden — by default the plan is rejected; the new-Forceswitch overrides this when the pin really is intentional.Breaking changes for tooling muscle memory
scripts/release-crate.ps1is removed; usescripts/release-packages.ps1.-Bump major|minor|patchis gone; supply-Packages '<name>@<change-spec>', ...instead.Crate*→Package*symbol rename swept the release tooling (release-record property, function names, log strings). Cargo's own terminology (crates/,crates.io,Cargo.toml,cargo metadata) is preserved verbatim.Documentation and tests
Release vocabulary, workflow, and invariants now live in
docs/releasing.md;AGENTS.mdshrinks to a list of pointers. A Pester suite underscripts/tests/Pester/covers pure helpers, BFS / aggregation analyses on synthetic workspaces, and agent-driven end-to-end interactive scenarios. Runs locally viajust test-scriptsand on Windows + Linux in thescript-testsCI job.