Skip to content

feat: prepare 0.3.0 publish infrastructure#27

Merged
EffortlessSteven merged 2 commits into
mainfrom
publish-infra
Feb 28, 2026
Merged

feat: prepare 0.3.0 publish infrastructure#27
EffortlessSteven merged 2 commits into
mainfrom
publish-infra

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

  • Add 5 missing microcrates to PUBLISH_CRATES in topological order (37 total)
  • Set tooling crates (feature-grid, test-grid, bdd-steps) to publish = false
  • Add [package.metadata.docs.rs] to 25 publishable crates
  • Create GitHub Actions release workflow (cipublishrelease)
  • Add cargo xtask publish command — single source of truth for the crate list, replacing the inline bash array in the workflow
  • Fix broken intra-doc links (public_jwkSelf::public_jwk)

Test plan

  • Linux CI passes (fmt, clippy, tests, feature-matrix, bdd, mutants, fuzz, publish-preflight)
  • cargo xtask publish-preflight succeeds (validates metadata + cargo package --no-verify)
  • cargo check -p xtask confirms the new Publish variant compiles
  • After merge: tag v0.3.0 and push to trigger release workflow

- Add 5 missing microcrates to PUBLISH_CRATES in topological order (37 total)
- Set tooling crates (feature-grid, test-grid, bdd-steps) to publish = false
- Add [package.metadata.docs.rs] to 25 publishable crates
- Create GitHub Actions release workflow (ci → publish → release)
- Fix broken intra-doc links (public_jwk → Self::public_jwk)
- Add 5 missing microcrates to PUBLISH_CRATES in topological order (37 total)
- Set tooling crates (feature-grid, test-grid, bdd-steps) to publish = false
- Add [package.metadata.docs.rs] to 25 publishable crates
- Create GitHub Actions release workflow (ci -> publish -> release)
- Add cargo xtask publish command (single source of truth for crate list)
- Fix broken intra-doc links (public_jwk -> Self::public_jwk)
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Feb 28, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

Release Notes

  • New Features

    • Automated GitHub Actions workflow for release processes, including continuous integration verification, automated crate publishing, and GitHub release creation
    • New crate publishing command with automatic retry logic and error recovery for handling transient failures during publishing
  • Documentation

    • Enhanced documentation generation configuration across crates to improve docs.rs builds
  • Chores

    • Adjusted publishing settings for select crates

Walkthrough

Introduces release automation infrastructure for the Rust project with a GitHub Actions workflow (ci → publish → release pipeline), configures documentation builds across crates via docs.rs metadata, disables publication for test/feature grid crates, adds a publish CLI command with retry logic to xtask, and updates minor documentation references.

Changes

Cohort / File(s) Summary
Release Workflow Infrastructure
.github/workflows/release.yml
New GitHub Actions workflow automating CI, crate publishing, and GitHub release creation with job dependencies and secret-based token management (67 lines).
Docs.rs Configuration (std feature)
crates/uselesskey-core-cache/Cargo.toml, crates/uselesskey-core-id/Cargo.toml, crates/uselesskey-core-negative-pem/Cargo.toml, crates/uselesskey-core-negative/Cargo.toml, crates/uselesskey-core-seed/Cargo.toml, crates/uselesskey-core-x509-negative/Cargo.toml, crates/uselesskey-core/Cargo.toml
Added [package.metadata.docs.rs] with features = ["std"] to enable std feature during docs.rs builds.
Docs.rs Configuration (all-features)
crates/uselesskey-core-factory/Cargo.toml, crates/uselesskey-core-hash/Cargo.toml, crates/uselesskey-core-jwk-builder/Cargo.toml, crates/uselesskey-core-jwk-shape/Cargo.toml, crates/uselesskey-core-jwk/Cargo.toml, crates/uselesskey-core-jwks-order/Cargo.toml, crates/uselesskey-core-keypair-material/Cargo.toml, crates/uselesskey-core-keypair/Cargo.toml, crates/uselesskey-core-kid/Cargo.toml, crates/uselesskey-core-sink/Cargo.toml, crates/uselesskey-core-token-shape/Cargo.toml, crates/uselesskey-core-token/Cargo.toml, crates/uselesskey-core-x509-derive/Cargo.toml, crates/uselesskey-core-x509-spec/Cargo.toml, crates/uselesskey-core-x509/Cargo.toml, crates/uselesskey-jwk/Cargo.toml, crates/uselesskey-pgp/Cargo.toml, crates/uselesskey-token/Cargo.toml
Added [package.metadata.docs.rs] with all-features = true to enable all features during docs.rs builds.
Publication Settings
crates/uselesskey-bdd-steps/Cargo.toml, crates/uselesskey-feature-grid/Cargo.toml, crates/uselesskey-test-grid/Cargo.toml
Changed publish flag from true to false to prevent these crates from being published to crates.io.
Documentation Comment Updates
crates/uselesskey-ecdsa/src/keypair.rs, crates/uselesskey-ed25519/src/keypair.rs, crates/uselesskey-rsa/src/keypair.rs
Updated alias method documentation to reference Self::public_jwk instead of public_jwk for consistency.
Publish Command Implementation
xtask/src/main.rs
Added Cmd::Publish variant and publish() function implementing crate publishing to crates.io with retry logic (3 attempts per crate, 60s wait on transient errors, 30s indexing wait post-success).

Sequence Diagram

sequenceDiagram
    participant Tag as Git Tag Push
    participant GHA as GitHub Actions
    participant CI as CI Job
    participant Cargo as Cargo Registry
    participant Pub as Publish Job
    participant GH as GitHub API
    participant Rel as Release Job

    Tag->>GHA: Trigger on v*
    GHA->>CI: Start CI Job
    CI->>CI: Setup Rust (stable/nightly)
    CI->>CI: Install NASM & Tools
    CI->>CI: Run cargo xtask ci
    CI->>GHA: ✓ CI Complete
    
    GHA->>Pub: Start Publish Job (after CI)
    Pub->>Pub: Setup Rust & NASM
    Pub->>Cargo: cargo publish (with retry logic)
    Cargo->>Pub: ✓ Published
    Pub->>GHA: ✓ Publish Complete
    
    GHA->>Rel: Start Release Job (after Publish)
    Rel->>GH: gh release create
    GH->>GH: Generate notes from tag
    GH->>Rel: ✓ Release Created
    Rel->>GHA: ✓ Release Complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A workflow springs to life so fine,
CI tests the code, then crosses the line,
Crates published with retry grace,
Releases tagged in cyberspace!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: prepare 0.3.0 publish infrastructure' accurately summarizes the main objective of the PR, which is to set up the infrastructure needed for version 0.3.0 release including GitHub Actions workflow, cargo xtask publish command, and Cargo.toml metadata updates.
Description check ✅ Passed The description directly addresses the changeset with clear bullet points covering all major components: PUBLISH_CRATES updates, publish flags, docs.rs metadata, GitHub Actions workflow, cargo xtask command, and intra-doc link fixes.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch publish-infra

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 40-42: Move the CARGO_REGISTRY_TOKEN out of the job-level env
block and instead add it only to the specific step that executes the publish
command (the step that runs "cargo xtask publish") so the token is scoped to
that single step; remove CARGO_REGISTRY_TOKEN from the job env and add an env
entry CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} under the step
that runs the publish command (the "cargo xtask publish" step) to restrict
exposure.

In `@crates/uselesskey-feature-grid/Cargo.toml`:
- Around line 13-15: The Cargo.toml for the uselesskey-feature-grid crate
includes a stale documentation field even though publish = false; remove the
documentation = "https://docs.rs/uselesskey-feature-grid" entry from the
manifest (or replace it with a local/internal URL if you intentionally want a
doc link) so the unpublished crate doesn't reference a non-existent docs.rs
page—look for the publish and documentation keys in the Cargo.toml to make this
change.

In `@xtask/src/main.rs`:
- Around line 252-259: The publish order lists dependent crate
"uselesskey-core-token" before its dependency "uselesskey-core-token-shape",
which can break publish; update the list so "uselesskey-core-token-shape"
appears before "uselesskey-core-token" (and verify any other dependency pairs
like "uselesskey-core-jwk-shape" vs "uselesskey-core-jwk-builder") so that each
crate's dependencies are listed earlier than the crate that depends on them.
- Around line 311-322: The publish step currently bails on non-retriable stderr
contents; update the logic around the stderr handling in xtask/src/main.rs (the
block using the stderr variable and the bail!("{name} publish failed...") call)
to treat crate-already-published errors as success instead of failure: detect
substrings like "already uploaded", "already exists", "already published",
"duplicate", and/or "already uploaded by another client" (case-insensitive) and,
when present, log an informational message (e.g., that the crate was already
published) and continue rather than calling bail!; keep the existing
retry/backoff for true transient errors (the current retriable logic) and only
convert the explicit already-published messages into non-failing outcomes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63688a7 and 18d2c8e.

📒 Files selected for processing (33)
  • .github/workflows/release.yml
  • crates/uselesskey-bdd-steps/Cargo.toml
  • crates/uselesskey-core-cache/Cargo.toml
  • crates/uselesskey-core-factory/Cargo.toml
  • crates/uselesskey-core-hash/Cargo.toml
  • crates/uselesskey-core-id/Cargo.toml
  • crates/uselesskey-core-jwk-builder/Cargo.toml
  • crates/uselesskey-core-jwk-shape/Cargo.toml
  • crates/uselesskey-core-jwk/Cargo.toml
  • crates/uselesskey-core-jwks-order/Cargo.toml
  • crates/uselesskey-core-keypair-material/Cargo.toml
  • crates/uselesskey-core-keypair/Cargo.toml
  • crates/uselesskey-core-kid/Cargo.toml
  • crates/uselesskey-core-negative-pem/Cargo.toml
  • crates/uselesskey-core-negative/Cargo.toml
  • crates/uselesskey-core-seed/Cargo.toml
  • crates/uselesskey-core-sink/Cargo.toml
  • crates/uselesskey-core-token-shape/Cargo.toml
  • crates/uselesskey-core-token/Cargo.toml
  • crates/uselesskey-core-x509-derive/Cargo.toml
  • crates/uselesskey-core-x509-negative/Cargo.toml
  • crates/uselesskey-core-x509-spec/Cargo.toml
  • crates/uselesskey-core-x509/Cargo.toml
  • crates/uselesskey-core/Cargo.toml
  • crates/uselesskey-ecdsa/src/keypair.rs
  • crates/uselesskey-ed25519/src/keypair.rs
  • crates/uselesskey-feature-grid/Cargo.toml
  • crates/uselesskey-jwk/Cargo.toml
  • crates/uselesskey-pgp/Cargo.toml
  • crates/uselesskey-rsa/src/keypair.rs
  • crates/uselesskey-test-grid/Cargo.toml
  • crates/uselesskey-token/Cargo.toml
  • xtask/src/main.rs

Comment on lines +40 to +42
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
steps:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scope CARGO_REGISTRY_TOKEN to the publish step only.

Setting the crates.io token at job scope exposes it to all steps/actions in the publish job. Restrict it to the single step that runs cargo xtask publish.

Suggested hardening change
   publish:
     needs: ci
     runs-on: ubuntu-latest
-    env:
-      CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
     steps:
       - uses: actions/checkout@v4
@@
       - name: Publish all crates
+        env:
+          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
         run: cargo xtask publish
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
steps:
publish:
needs: ci
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Publish all crates
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo xtask publish
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 40 - 42, Move the
CARGO_REGISTRY_TOKEN out of the job-level env block and instead add it only to
the specific step that executes the publish command (the step that runs "cargo
xtask publish") so the token is scoped to that single step; remove
CARGO_REGISTRY_TOKEN from the job env and add an env entry CARGO_REGISTRY_TOKEN:
${{ secrets.CARGO_REGISTRY_TOKEN }} under the step that runs the publish command
(the "cargo xtask publish" step) to restrict exposure.

Comment on lines +13 to 15
publish = false
readme = "README.md"
documentation = "https://docs.rs/uselesskey-feature-grid"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: Stale documentation URL for unpublished crate.

Setting publish = false is correct for this tooling crate. However, the documentation field on line 15 still points to docs.rs/uselesskey-feature-grid, which will never exist since this crate won't be published.

Consider removing the documentation field for clarity, or leave it if you prefer consistency across all manifests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-feature-grid/Cargo.toml` around lines 13 - 15, The
Cargo.toml for the uselesskey-feature-grid crate includes a stale documentation
field even though publish = false; remove the documentation =
"https://docs.rs/uselesskey-feature-grid" entry from the manifest (or replace it
with a local/internal URL if you intentionally want a doc link) so the
unpublished crate doesn't reference a non-existent docs.rs page—look for the
publish and documentation keys in the Cargo.toml to make this change.

Comment thread xtask/src/main.rs
Comment on lines +252 to +259
"uselesskey-core-negative-pem",
"uselesskey-core-negative",
"uselesskey-core-sink",
"uselesskey-core-token",
"uselesskey-core-token-shape",
"uselesskey-core-jwk-shape",
"uselesskey-core-jwks-order",
"uselesskey-core-jwk-builder",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix publish order: dependency appears after dependent crate.

uselesskey-core-token depends on uselesskey-core-token-shape (see crates/uselesskey-core-token/Cargo.toml), but Line 255 currently lists uselesskey-core-token before uselesskey-core-token-shape. This can make publish fail before reaching the needed dependency.

Suggested ordering fix
-    "uselesskey-core-token",
-    "uselesskey-core-token-shape",
+    "uselesskey-core-token-shape",
+    "uselesskey-core-token",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"uselesskey-core-negative-pem",
"uselesskey-core-negative",
"uselesskey-core-sink",
"uselesskey-core-token",
"uselesskey-core-token-shape",
"uselesskey-core-jwk-shape",
"uselesskey-core-jwks-order",
"uselesskey-core-jwk-builder",
"uselesskey-core-negative-pem",
"uselesskey-core-negative",
"uselesskey-core-sink",
"uselesskey-core-token-shape",
"uselesskey-core-token",
"uselesskey-core-jwk-shape",
"uselesskey-core-jwks-order",
"uselesskey-core-jwk-builder",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtask/src/main.rs` around lines 252 - 259, The publish order lists dependent
crate "uselesskey-core-token" before its dependency
"uselesskey-core-token-shape", which can break publish; update the list so
"uselesskey-core-token-shape" appears before "uselesskey-core-token" (and verify
any other dependency pairs like "uselesskey-core-jwk-shape" vs
"uselesskey-core-jwk-builder") so that each crate's dependencies are listed
earlier than the crate that depends on them.

Comment thread xtask/src/main.rs
Comment on lines +311 to +322
let stderr = String::from_utf8_lossy(&output.stderr);
let retriable = stderr.contains("failed to select a version")
|| stderr.contains("no matching package")
|| stderr.to_lowercase().contains("not found");

if retriable {
eprintln!(" Dependency indexing race detected. Waiting 60s before retry...");
std::thread::sleep(std::time::Duration::from_secs(60));
} else {
eprint!("{stderr}");
bail!("{name} publish failed with a non-retriable error");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make publish resumable after partial success.

If a run partially publishes crates and then fails, rerunning currently aborts on “already published/already exists” errors as non-retriable. That blocks recovery for tag workflows. Treat those cases as success and continue.

Suggested resiliency fix
             let stderr = String::from_utf8_lossy(&output.stderr);
-            let retriable = stderr.contains("failed to select a version")
+            let stderr_lc = stderr.to_lowercase();
+            let already_published = stderr_lc.contains("already uploaded")
+                || stderr_lc.contains("already exists")
+                || stderr_lc.contains("already published");
+            let retriable = stderr.contains("failed to select a version")
                 || stderr.contains("no matching package")
-                || stderr.to_lowercase().contains("not found");
+                || stderr_lc.contains("not found");
+
+            if already_published {
+                eprintln!("    {name} is already published; continuing.");
+                success = true;
+                break;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let stderr = String::from_utf8_lossy(&output.stderr);
let retriable = stderr.contains("failed to select a version")
|| stderr.contains("no matching package")
|| stderr.to_lowercase().contains("not found");
if retriable {
eprintln!(" Dependency indexing race detected. Waiting 60s before retry...");
std::thread::sleep(std::time::Duration::from_secs(60));
} else {
eprint!("{stderr}");
bail!("{name} publish failed with a non-retriable error");
}
let stderr = String::from_utf8_lossy(&output.stderr);
let stderr_lc = stderr.to_lowercase();
let already_published = stderr_lc.contains("already uploaded")
|| stderr_lc.contains("already exists")
|| stderr_lc.contains("already published");
let retriable = stderr.contains("failed to select a version")
|| stderr.contains("no matching package")
|| stderr_lc.contains("not found");
if already_published {
eprintln!(" {name} is already published; continuing.");
success = true;
break;
}
if retriable {
eprintln!(" Dependency indexing race detected. Waiting 60s before retry...");
std::thread::sleep(std::time::Duration::from_secs(60));
} else {
eprint!("{stderr}");
bail!("{name} publish failed with a non-retriable error");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtask/src/main.rs` around lines 311 - 322, The publish step currently bails
on non-retriable stderr contents; update the logic around the stderr handling in
xtask/src/main.rs (the block using the stderr variable and the bail!("{name}
publish failed...") call) to treat crate-already-published errors as success
instead of failure: detect substrings like "already uploaded", "already exists",
"already published", "duplicate", and/or "already uploaded by another client"
(case-insensitive) and, when present, log an informational message (e.g., that
the crate was already published) and continue rather than calling bail!; keep
the existing retry/backoff for true transient errors (the current retriable
logic) and only convert the explicit already-published messages into non-failing
outcomes.

@EffortlessSteven EffortlessSteven merged commit b14b8d3 into main Feb 28, 2026
3 of 4 checks passed
@EffortlessSteven EffortlessSteven deleted the publish-infra branch February 28, 2026 20:03
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