feat(scripts): detect modified-but-unreleased upstream deps during release#436
feat(scripts): detect modified-but-unreleased upstream deps during release#436sandersaares wants to merge 24 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 286 286
Lines 22879 22978 +99
=======================================
+ Hits 22879 22978 +99 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
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>
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>
…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>
The replacement string `\\\\` was producing two literal backslashes before each escaped metacharacter (e.g. `1.2.3` -> `1\\.2\\.3`), because `\` is *not* a special character in .NET regex replacement-string syntax — it's literal — and the two `\\` characters in the PowerShell single- quoted string both pass through verbatim. The correct replacement is `\\`: one literal backslash plus the group-1 backreference, matching `[regex]::Escape` semantics and matching the sibling pattern at `Add-CascadeBulletToVersionSection`. This was latent because the only inputs (crate names) never contain any regex metacharacters, so the double-escape never fired. Fixed for consistency and to prevent future surprises if the helper is reused. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
scripts/release-crate.ps1 now fully automates the version-bump cascade (non-dev workspace dependents, root Cargo.toml updates, breaking-vs-patch classification) that the prompt was guiding humans/agents through. Also drops the now-stale reference to the prompt from release-crate.ps1's synopsis. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| } | ||
| } | ||
|
|
||
| Set-Content -LiteralPath $ChangelogFile -Value $new -Encoding utf8 |
| is applied: enough to refresh the workspace-pinned version, but without overstating | ||
| the change to downstream consumers. | ||
| Dev-only dependents are skipped — they automatically pick up the new workspace version. | ||
| This mirrors the guidance in `.github/prompts/bump-crate-version.prompt.md`. | ||
| 3. Changelog Generation: A CHANGELOG.md entry is generated for the target and every cascaded | ||
| dependent. Cascaded crates that have no other commits since their last release get a single | ||
| `bump \`<target>\` to <new-version>` entry under `🔧 Maintenance` (or `⚠️ Breaking` for | ||
| major bumps). | ||
| `Now requires <new-version> of \`<target>\`` entry under `🔧 Maintenance` (or `⚠️ Breaking` | ||
| for major bumps). |
Replace "file(s)", "crate(s)", "finding(s)", "commit(s)", "package(s)", "test group(s)", "example(s)" with the bare plural. Accept the cosmetic "1 crates" case rather than the wishy-washy parenthetical. Applies the rule consistently across all script outputs (both .ps1 and .rs), not just the lines in this PR's diff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nvoke-PostReleaseDepScan The interactive post-release scan computes $new (findings to prompt for) once at the top of each outer iteration. Within the foreach over $new, accepting a release calls Invoke-ReleaseFlow, which can cascade-bump other crates that are still pending in the same $new. The loop previously continued prompting for those crates, producing the misleading "Leaving X unreleased" message even though X had just been released via cascade. Track the current release set in a HashSet seeded from Get-CratesWithVersionBumps and grown after every nested Invoke-ReleaseFlow. Before each Read-Host, skip with a clear "cascade-bumped by a prior release in this run (now at <version>) — skipping prompt" message when the entry is now in the release set. Adds a T17 row to the manual test cases doc covering the regression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduces scripts/tests/Pester/ with:
* Run-Tests.ps1 entry point that validates Pester 5.7+ availability,
discovers *.Tests.ps1 under unit/, integration/, scenarios/, and
emits NUnit XML for CI consumption.
* _common/TestHelpers.ps1 with Get-OxiRepoRoot for deterministic path
resolution (set via OXI_TEST_COMMON in Run-Tests.ps1).
* _common/New-SyntheticWorkspace.ps1 — a synthetic Cargo-workspace
fixture builder with 9 topology presets (Linear2/3/4, Diamond4,
Macros3, FanOut5, UpDown5, Mixed6, Detached) and ad-hoc -Spec
support. Workspaces use workspace inheritance (foo.workspace = true)
to mirror production layout and avoid a latent Update-CrateVersion
bug pinned by Phase 5.
* unit/releasing/Smoke.Tests.ps1 sanity check for shared-library
loading.
* integration/Topology-Presets.Tests.ps1 — one round-trip test per
preset covering BFS, dev-dep filtering, publish=false filtering,
and disconnected-component bleed prevention.
Adds a 'just test-scripts' recipe; the existing 'just install-tools'
recipe now installs Pester 5.7+ idempotently. DEVELOPMENT.md lists
Pester among the prerequisites and points at 'just test-scripts'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pulls the inline entrypoint of release-crate.ps1 into two new functions so
Pester scenarios can drive the full release flow in-process:
* Invoke-WorkspaceCheck wraps the post-release 'cargo check --workspace'
call. Tests Mock it; production code calls it inside Invoke-ReleaseMain.
* Invoke-ReleaseMain wraps input validation, pre-flight checks, GitHub
remote detection, base-ref resolution, the workflow, and the workspace
check. Returns the array of release records so tests can assert on
final state. The script-level execution block is now a single line
that invokes Invoke-ReleaseMain with the script-level parameters.
Also fixes a latent bug surfaced during refactor validation: when
Test-GitRef sets \ = '' because the base ref could not be
resolved, downstream calls to Invoke-CascadeStep, Invoke-ReleaseFlow,
and Invoke-PostReleaseDepScan would fail parameter binding with a
misleading 'Cannot bind argument' error. The internal short-circuit
'if (\ is empty) return' never fires because parameter
validation rejects the empty string before the body runs. Adding
[AllowEmptyString()] to BaseRef on all three functions lets the
intended skip path execute.
No behavior change for callers that always pass a valid BaseRef.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds 55 unit tests covering pure helpers in scripts/lib/releasing.ps1 and
scripts/release-crate.ps1:
* SemVer arithmetic: Compare-SemanticVersions, Get-NextVersion (incl.
Cargo 0.x.y and 0.0.z rules), Get-BumpKindFromVersions,
Test-IsBreakingChange.
* Input validation: Test-ValidVersion, Test-ValidCrateName.
* Workspace metadata helpers: Test-CrateExposesTarget,
Get-CrateFolderForPath.
* release-crate.ps1 helpers: Sort-KeysByPreferredOrder,
Format-ConventionalCommits (header grouping, breaking-change lift,
PR-link injection, ignored-type filtering, miscellaneous bucket).
To dot-source release-crate.ps1 without running its entrypoint, the
script now skips Invoke-ReleaseMain when the env var
OXI_RELEASE_CRATE_NOEXEC is set to "1". CrateName is no longer mandatory
at the script level — Invoke-ReleaseMain validates it after dispatch.
Production behavior is unchanged when the env var is unset.
Found two latent bugs (tracked for Phase 8 bug-bash):
* Compare-SemanticVersions hits an infinite loop on single-segment
inputs like "1" because PowerShell scalar += 0 never promotes to an
array. Production callers never pass such values (Test-ValidVersion
rejects them) so the bug is dormant, but the pad-to-3 loop is unsafe
and should be rewritten using [System.Version] or explicit array
construction.
* Update-CrateVersion crate-level version-rewrite regex is overly
broad and would clobber declared versions of inline workspace deps
(Phase 2 smoke notes). Dormant in production because real crates use
workspace inheritance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds 27 unit tests covering helpers that touch git or the filesystem in
scripts/lib/releasing.ps1 and scripts/release-crate.ps1:
* Test-GitRef — branch / HEAD / non-existent / SHA cases.
* Get-CurrentVersion — happy path and missing-file error.
* Get-CrateVersionFromRef — HEAD, prior commit, and non-existent
crate folder.
* Get-CrateLastReleaseBaseline — multi-commit history with source
edits surrounding a version-changing commit (verifies the right
commit SHA is returned).
* Get-WorkspaceCrates — Mixed6 preset coverage of publish=false and
dev-deps exclusion.
* Get-AllTransitiveDependents — Diamond4 dedup and Mixed6
publish=false exclusion.
* Get-CratesWithUnreleasedChanges — committed / working-tree /
untracked file paths, and publish=false skip.
* Get-CratesWithVersionBumps — version-differs and empty-set cases.
* Add-CascadeBulletToVersionSection — Maintenance vs Breaking
sub-header selection, missing version section, missing changelog.
Each Describe block calls Invalidate-WorkspaceMetadataCache in BeforeAll
to avoid bleed from a prior fixture; tests that build their own ad-hoc
workspace inside an It also invalidate first.
Full suite is now 93/93 green in ~82s on Windows.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 20 Pester integration tests under scripts/tests/Pester/integration/: - 13 BFS/topology tests re-encoding the N1..N9 scenarios from the manual RELEASE-DEPS-TEST-CASES.md harness (plus Linear4/Diamond4/Detached topology variants). - 3 Update-CrateVersion tests, including a pin for the inline-dep workspace-version clobbering bug logged in Phase 8. - 4 Invoke-CascadeStep tests (fresh bump, sufficient pre-bump, upgrade insufficient pre-bump, missing crate warning). Two defensive fixes to support these tests: - `Invoke-CascadeStep` `-PrBaseUrl` gained `[AllowEmptyString()]` so empty strings pass through (production already calls it with empty values when `OXI_PR_BASE_URL` is unset). Same class of latent issue as the BaseRef fix in Phase 2. - `Invoke-Git` now disables `\$PSNativeCommandUseErrorActionPreference` locally; the function manages exit codes manually via `\$LASTEXITCODE` and was throwing prematurely under Run-Tests.ps1's strict-mode default. Suite size: 113 tests (Phases 1-5), full green in ~140s. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a PSD1-driven scenario runner that exercises the full
release-crate.ps1 entrypoint under mocked prompts and a synthetic
workspace, and lands seven initial scenarios covering smoke, clean
upstream, accept/decline mix, decline-all, non-interactive mode,
diamond aggregation, and the cascade-mid-foreach skip path.
Each .scenario.psd1 declares its topology (preset or custom spec),
history transformations, the release invocation, scripted prompt
answers, and an expected outcome (released crates + versions + raised
prompts + unconsumed answers). The runner mocks Read-Host,
Invoke-WorkspaceCheck, and Test-InteractiveSession, replays the
scenario history, then calls Invoke-ReleaseMain in-process so each
scenario runs in under a second.
While building the scenarios I uncovered a real UX bug in
Get-UnreleasedModifiedDependencies: \ was a plain Hashtable,
whose enumeration order is non-deterministic across processes. That
made the post-release prompt order flaky in production (users would
see prompts in different orders run-to-run) and impossible to assert
in tests. Fix is two parts:
- \ is now an [ordered]@{} keyed on folder, so BFS
insertion order is preserved when iterating .Values.
- The release-set foreach now sorts the input HashSet
alphabetically so the BFS roots themselves are visited in a
stable order.
OrderedDictionary uses .Contains(key) instead of .ContainsKey(key);
updated the membership check accordingly.
113 prior tests + 7 scenarios = 120 green.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a new `script-tests` job to .github/workflows/main.yml that runs the Pester suite (`scripts/tests/Pester/`) on ubuntu-latest and windows-latest. The job has no delta gate (delta tracks Rust crates and would mark script-only PRs as skippable, which is exactly the opposite of what we want here). It installs Pester 5.7.1+ inline using the same incantation as `just install-tools` and reuses the shared Setup composite action for cargo (the synthetic-workspace fixture builder shells out to `cargo metadata`). The new job is added to `required-checks.needs` so branch protection keeps requiring only that single context per AGENTS.md policy. It also has a strategy.matrix, so being part of the fan-in correctly avoids the stuck-context bug for the matrix-expanded contexts. Also tightens documentation: - DEVELOPMENT.md: add `just test-scripts` to the Linux validation list for symmetry with the Windows list (already had it). - RELEASE-DEPS-TEST-CASES.md: reframe as a historical/reference document; most behaviours are now pinned by the automated Pester suite, but the prose specification is still useful when diagnosing against the real workspace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Problem
When releasing a workspace 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 against the last released version of each dependency on
crates.io — missing the new changes.
Crucially, those upstream modifications may have been introduced in an
earlier PR that merged to
mainwithout a version bump, not necessarilythe current PR. A diff of the current branch vs the PR base would never see
them, but they are just as unreleased as same-PR changes.
The existing cascade in
release-crate.ps1propagates bumps downstream(target -> its dependents) but never upstream. This PR closes that gap.
Solution
Two layers of automation, sharing logic via the new
scripts/lib/releasing.ps1library.1. Interactive layer —
release-crate.ps1After the existing downstream cascade finishes, the script scans the release
set (every crate whose version differs from the PR base ref) for transitive
workspace dependencies that:
For each finding the author is prompted
[y/N]and (if yes) picks a bumpkind. Accepted crates are queued and released next, each with their own
cascade and re-scan. Declined crates are recorded so they aren''t re-prompted.
2. CI layer —
release-depsjob.github/workflows/main.ymlgains arelease-depsjob that runsscripts/check-unreleased-dependencies.ps1(the same analysis,non-interactive). When findings exist it posts a sticky PR comment listing
each modified-but-unreleased dependency with the chain that reaches it, so
reviewers can sanity-check materiality decisions. When there are no findings
the script exits fast, so the job always runs and stays cheap.
Per-crate baseline
"Modified" is evaluated per crate against that crate''s own last release
boundary, not against the PR base ref. For each crate the analysis runs:
That returns the most recent commit whose diff touched the crate''s top-level
version =orpublish =line. Any change undercrates/<folder>/newerthan that commit — committed, working-tree, or untracked — is treated as
unreleased and feeds into the BFS from each release-set crate.
The release-set itself (
Get-CratesWithVersionBumps) intentionally stilldiffs against the PR base ref — that''s the correct anchor for "what is this
PR releasing".
Tags are not consulted: a CI clone or partial fetch may not have them, and
the
Cargo.tomledit is the canonical cause of a release while a tag isdownstream evidence.
Notable design points
scripts/lib/releasing.ps1is a pure dot-source library (no top-levelparam(), no side effects). Both entry-point scripts dot-source it.dev-dependenciesare excluded (cannot affect downstream consumers via crates.io).
sufficient version appends a maintenance bullet to its existing changelog
section instead of double-bumping. Bullet format is
Now requires \<version>\ of \<target>\(consistent across both thefresh-bump and re-cascade emission paths).
gitinvocations go throughInvoke-Git(array-argument, noInvoke-Expression). The legacy wrapper was dropped in commit 2.Manual test matrix
T1-T16 (single-PR / base-ref-relative) and N1-N10 (multi-PR / per-crate
baseline) scenarios are documented and reproducible from
scripts/tests/RELEASE-DEPS-TEST-CASES.md. All were exercised end-to-end againsta scratch worktree before commit; the N-series in particular covers the
prior-PR-without-bump case that motivated the per-crate baseline.
Files
scripts/lib/releasing.ps1,scripts/check-unreleased-dependencies.ps1,scripts/tests/RELEASE-DEPS-TEST-CASES.mdscripts/release-crate.ps1,.github/workflows/main.yml