Skip to content

Filter publish output by registry version (cargo info)#21

Merged
SVilgelm merged 5 commits into
mainfrom
filter-publish-by-version
May 7, 2026
Merged

Filter publish output by registry version (cargo info)#21
SVilgelm merged 5 commits into
mainfrom
filter-publish-by-version

Conversation

@SVilgelm
Copy link
Copy Markdown
Member

@SVilgelm SVilgelm commented May 7, 2026

Summary

  • publish previously listed every crate with publish != false, so a cargo publish -p ${{ matrix.package }} matrix step would hit "crate version already uploaded" until a human pruned it.
  • parseMetadata now returns publishCandidates: [{name, version, registry}] and a new async filterPublishable runs cargo info <name> (with --registry when scoped) for each candidate, keeping only those whose local version is strictly greater per a full semver §11 compare.
  • Crates absent from the registry are kept (first publish); other cargo info failures log a core.warning and skip the candidate, so a transient outage cannot republish a stale crate.

Test plan

  • npm test — 28/28 pass, including new compareSemver and parseCargoInfoVersion cases.
  • npm run check — prettier clean.
  • npm run builddist/index.js rebuilt and committed.
  • Smoke test in a workflow against a multi-crate workspace where one crate is bumped and another isn't; confirm only the bumped crate appears in publish.
  • Smoke test against a brand-new crate not yet on crates.io; confirm it appears in publish (first-publish path).

… registry

Previously the `publish` output contained every crate with `publish != false`,
even when the local Cargo.toml version matched what was already on the
registry — so a `cargo publish -p ${{ matrix.package }}` step would fail with
"crate version already uploaded" until a human pruned the matrix.

`parseMetadata` now returns `publishCandidates: [{name, version, registry}]`
and a new async `filterPublishable` runs `cargo info <name>` (with
`--registry` when the candidate is registry-restricted) for each candidate,
keeping only those whose local version is strictly greater per a full
semver §11 compare. Crates absent from the registry are kept (first publish);
other `cargo info` failures log a warning and skip the candidate, so a
transient outage cannot republish a stale crate.

Tests: rewrote publish-related cases for the new shape and added unit tests
for `compareSemver` (prerelease precedence, numeric vs lexical, build
metadata) and `parseCargoInfoVersion`. All 28 tests pass; dist rebuilt.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Copilot AI review requested due to automatic review settings May 7, 2026 17:49
New `publish` output filter (only crates with a newer-than-registry version)
is a user-visible feature addition; bump minor.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refines the action’s publish output to include only workspace crates whose local Cargo.toml version is strictly newer than the latest version on the target registry, reducing “version already uploaded” failures in publish matrices.

Changes:

  • parseMetadata now emits publishCandidates with {name, version, registry} instead of a flat publish list.
  • Added semver-precedence comparison (compareSemver) plus cargo info parsing and a new filterPublishable step to compute the final publish list.
  • Updated docs and tests, and rebuilt the bundled dist/index.js.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lib.js Adds semver comparison + cargo info-based filtering and updates outputs accordingly.
tests/parse-metadata.test.js Updates metadata expectations and adds unit tests for semver compare and cargo info parsing.
README.md Documents new publish semantics and filtering behavior.
action.yml Updates publish output description to match the new behavior.
dist/index.js Rebuilt bundle reflecting the lib.js changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib.js Outdated
Comment thread lib.js Outdated
Comment thread lib.js Outdated
SVilgelm added 2 commits May 7, 2026 11:00
…ixture

Two related Fixtures-CI bugs surfaced once the publish output started using
cargo info:

1. `cargo info <name>` (no `--registry`) probes the workspace at `cwd`
   first and returns the *local* member's version when the name matches.
   That made `local == published` for every workspace candidate and
   silently emptied the publish output. Always pass `--registry` (default
   `crates-io`) so cargo skips the local lookup.

2. The workspace-mixed fixture's `app` package collided with the real
   `app` crate on crates.io (already at 0.6.5), so the fixture's 0.1.0 was
   correctly classified as stale and excluded — but the workflow asserted
   the opposite. Rename the fixture to `rma-fixture-app` (with a comment
   explaining why) so it's guaranteed to be a first-publish, and update
   the workspace-mixed assertions accordingly.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Three issues called out by the PR-21 review:

1. Multi-registry candidates (`publish = ["a","b"]`) only had their first
   entry queried, so a crate stale on registry "b" but absent from "a"
   could still appear in the publish list. parseMetadata now emits the
   full `registries: string[]` (with `[null]` for unrestricted), and the
   new `getMaxPublishedVersion` queries every entry and compares against
   the highest version found.

2. `getPublishedVersion` resolved to `null` when `cargo info` exited 0 but
   the parser couldn't find a `version:` line — `filterPublishable` then
   treated that as "first publish" and included the crate, which would
   mass-republish on any future cargo output drift. Reject in that path
   instead, so the catch path warns and skips (safe default).

3. `Promise.all` over every candidate spawned one `cargo info` per crate
   concurrently. Replaced with a small `asyncPool` (limit 4) so resource
   use stays predictable on large workspaces and we don't trip registry
   rate limits.

Tests: updated publish-candidate fixtures to assert `registries: [...]`,
added a multi-registry test case, plus three asyncPool tests (order,
concurrency cap, empty input). 32/32 pass; dist rebuilt.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Comment thread dist/index.js Fixed
`asyncPool`'s JSDoc already documented the worker as `worker(item)`, but
the call site was `worker(items[i], i)` — passing an index that no caller
consumes. Flagged by github-code-quality[bot] on PR 21. Drop the second
arg so code matches the documented signature; trivially re-addable if a
future caller actually wants the index.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
@SVilgelm SVilgelm merged commit 7c4e86d into main May 7, 2026
15 checks passed
@SVilgelm SVilgelm deleted the filter-publish-by-version branch May 7, 2026 18:11
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