Skip to content

refactor: extract providers and core types into fnox-core crate#458

Merged
jdx merged 5 commits intomainfrom
extract-core-crate
May 2, 2026
Merged

refactor: extract providers and core types into fnox-core crate#458
jdx merged 5 commits intomainfrom
extract-core-crate

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented May 2, 2026

Summary

Splits fnox into a Cargo workspace with a reusable fnox-core library crate alongside the existing CLI binary. The library half of fnox (providers, config types, secret resolver, lease backends, settings) now lives in crates/fnox-core/ and can be depended on by other Rust projects without pulling in the CLI surface (commands, MCP server, TUI, shell integration, hook-env machinery).

This is a pure refactor — no behavior change. Public Rust paths (fnox::providers::*, fnox::config::*, fnox::Fnox, etc.) continue to work via re-exports from the binary crate's lib.rs, so existing library consumers don't have to update imports.

What moved to fnox-core

  • providers/ (every provider implementation, the Provider trait, the wizard metadata, the resolver)
  • config.rs, secret_resolver.rs, auth_prompt.rs
  • lease.rs, lease_backends/, http.rs
  • settings.rs, error.rs, env.rs, source_registry.rs, spanned.rs, suggest.rs, temp_file_secrets.rs
  • library.rs (the Fnox convenience API)
  • build.rs + build/generate_*.rs, providers/*.toml, settings.toml (build inputs)

What stays in the root fnox crate

  • main.rs, commands/, mcp_server.rs, tui/, hook_env.rs, shell/
  • A thin lib.rs that re-exports every fnox-core module so existing paths keep working

Other changes

  • src/main.rs was double-declaring every module via mod alongside lib.rs's pub mod; cleaned up to be a thin shim that uses the library crate.
  • Workspace dependencies declared once in the root Cargo.toml; both crates pull from them via workspace = true to keep versions consistent.
  • Trimmed root Cargo.toml to only the deps the binary uses directly (a follow-up commit on top of the move).
  • One pre-existing test (provider_add_types_match_provider_definitions in commands/provider/mod.rs) needed its CARGO_MANIFEST_DIR-relative path updated to point at crates/fnox-core/providers.

Test plan

  • cargo build --workspace clean
  • cargo test --workspace — 147 tests in fnox-core, 8 in fnox, all pass
  • mise run lint — all hk checks (cargo-fmt, cargo-clippy -D warnings, prettier, actionlint, shfmt, shellcheck, pkl) pass
  • mise run test:bats — 602 pass, 17 keychain failures pre-existing macOS interactive-auth limitation, 7 lease tests skipped (LocalStack not running locally). Same baseline as main.
  • ./target/debug/fnox --version and fnox provider list smoke tests
  • CI on this branch (let CI verify the same on Linux/musl/etc.)

🤖 Generated with Claude Code


Note

Medium Risk
Moderate risk because it restructures the crate into a workspace and changes build/test/release plumbing; behavior should be unchanged but mis-wired deps, paths, or logging filters could break CI, publishing, or downstream imports.

Overview
Creates a Cargo workspace and extracts providers/config/secret-resolution/lease logic into a new fnox-core library crate, while the root fnox crate becomes a CLI-focused binary that depends on fnox-core and re-exports its public modules to preserve existing fnox::... import paths.

Updates CI and release automation to operate on the workspace (workspace-wide cargo test/clippy, MSRV verification note, release-plz now bumps workspace version and publishes fnox-core before fnox), adjusts a provider-definition test path for the moved providers/*.toml, and expands the default tracing filter to include fnox_core logs.

Reviewed by Cursor Bugbot for commit 9d2ee78. Bugbot is set up for automated code reviews on this repo. Configure here.

jdx added 2 commits May 2, 2026 12:31
Convert the repo into a Cargo workspace and split fnox into two crates:

- `fnox-core` (`crates/fnox-core/`) — provider implementations, config
  types, secret resolver, lease backends, settings, error types, and the
  `Fnox` library API. Reusable as a library by downstream consumers.
- `fnox` (root) — CLI binary plus the CLI-shaped modules (commands, MCP
  server, TUI, hook-env, shell integration). Depends on `fnox-core` and
  re-exports its modules from `lib.rs` so existing `fnox::providers`,
  `fnox::config`, etc. paths keep working.

Build infrastructure for provider and settings code generation, plus the
`providers/*.toml` and `settings.toml` inputs, move to the core crate so
their `OUT_DIR`-relative `include!` calls resolve correctly.

Workspace dependencies are declared in the root `Cargo.toml`; both crates
reference them with `workspace = true` to keep versions consistent.

`cargo build`, `cargo test --workspace`, and `mise run lint` all pass.
…irectly

Provider, config, secret-resolver, and lease crates moved to fnox-core in
the previous commit; the binary doesn't import them directly anymore. Drop
the duplicate workspace-dep listings from the root crate. Removed deps:
aes-gcm, age, arc-swap, async-trait, dirs, fslock, futures, hex,
jsonwebtoken, pluralizer, rand, reqwest, serde_spanned, sha2, shellexpand,
strsim, terminal_size, thiserror, urlencoding, walkdir, xx, plus the
linux-only dbus / openssl-sys (now declared in fnox-core where keyring
lives).

Workspace.dependencies in the root Cargo.toml stays as the full list so
both crates can keep version-pinning consistent via workspace = true.

cargo build, cargo test --workspace, and mise run lint all still pass.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 2, 2026

Greptile Summary

This PR is a pure structural refactor that extracts the reusable engine (providers, config, secret resolver, lease backends, settings) into a new fnox-core library crate, while the root fnox crate becomes a thin CLI binary that re-exports all fnox_core::* modules to preserve existing import paths. The workspace dependency split, release ordering, CI flags, and tracing filter update are all handled correctly.

Confidence Score: 5/5

Safe to merge — pure refactor with no behavior changes; only P2 findings.

All findings are P2 (test helper naming and a fragile but non-critical regression guard). No logic changes to production code paths.

crates/fnox-core/src/library.rs — regression-guard test is a no-op in this repo.

Important Files Changed

Filename Overview
Cargo.toml Restructured as workspace manifest + fnox package; workspace.dependencies centralises versions; platform-conditional deps correctly declared in workspace and applied under target guards in fnox-core.
crates/fnox-core/Cargo.toml New library crate manifest; all deps use workspace = true; platform-specific targets correctly guarded; build-dependencies preserved from old root build.rs.
crates/fnox-core/src/lib.rs New crate root; exports all moved modules; lease_backends doc mention was already flagged in a previous thread.
crates/fnox-core/src/library.rs New Fnox convenience API with intra-doc links fixed; regression-guard test for open() vs discover() is a no-op in this repo since fnox.toml is present at workspace root.
src/lib.rs Thin shim re-exporting all fnox_core modules so existing fnox::* import paths continue to work; clean separation from CLI-only modules.
src/main.rs Correctly extends tracing filter to include fnox_core so --verbose captures library-level logs; set_cli_snapshot called before any settings access.
mise-tasks/release-plz Correctly publishes fnox-core before fnox; uses cargo set-version --workspace to bump both crates atomically.
src/commands/provider/mod.rs CARGO_MANIFEST_DIR path correctly updated to point at crates/fnox-core/providers from the workspace root.
crates/fnox-core/src/auth_prompt.rs Moved from src/; minor name mismatch in test helper (provider_with_auth_command creates a provider with auth_command: None); logic and tests are otherwise correct.
.github/workflows/ci.yml Added --workspace to cargo test and cargo clippy; MSRV check left single-crate with explanatory comment; all changes correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    WS["Cargo Workspace (root Cargo.toml)"]
    WS --> FNOX["fnox (binary crate)\nsrc/main.rs, src/lib.rs\ncommands/, mcp_server, tui/\nhook_env, shell/"]
    WS --> CORE["fnox-core (library crate)\ncrates/fnox-core/\nproviders/, config, secret_resolver\nlease_backends, settings, library.rs"]
    FNOX -->|"depends on path + version"| CORE
    FNOX -->|"re-exports all fnox_core::* modules"| REEXPORT["fnox::providers, fnox::config, etc.\n(preserved paths)"]
    CORE --> PROV["Provider trait + 20 provider impls"]
    CORE --> CFG["Config / SecretResolver"]
    CORE --> LEASE["Lease + LeaseBackends"]
    CORE --> SETTINGS["Settings (ArcSwap)"]
    CORE --> API["Fnox convenience API\n(Fnox::discover / open / get / list)"]
    RELEASE["mise-tasks/release-plz"]
    RELEASE -->|"1. cargo publish -p fnox-core"| CORE
    RELEASE -->|"2. cargo publish -p fnox"| FNOX
Loading

Fix All in Claude Code

Reviews (3): Last reviewed commit: "fix(ci): drop --workspace from cargo msr..." | Re-trigger Greptile

Comment thread crates/fnox-core/src/lib.rs Outdated
Comment thread crates/fnox-core/Cargo.toml Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the project into a Cargo workspace by extracting the core provider logic, configuration types, and secret resolution into a new fnox-core crate. The root Cargo.toml has been updated to manage shared metadata and dependencies via workspace inheritance, and the main fnox binary now re-exports these core modules to maintain backward compatibility. The review feedback suggests further centralizing the package version within the workspace configuration and moving target-specific dependencies to the workspace level to ensure consistent version management across all crates.

Comment thread Cargo.toml
Comment on lines +5 to 10
[workspace.package]
edition = "2024"
authors = ["@jdx"]
description = "A flexible secret management tool supporting multiple providers and encryption methods"
license = "MIT"
repository = "https://github.com/jdx/fnox"
readme = "README.md"
rust-version = "1.91.1"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To fully leverage the workspace and ensure version consistency as mentioned in the PR summary, consider centralizing the package version. You can add version = "1.23.0" to the [workspace.package] section and then use version.workspace = true in both the root [package] and crates/fnox-core/Cargo.toml.

Suggested change
[workspace.package]
edition = "2024"
authors = ["@jdx"]
description = "A flexible secret management tool supporting multiple providers and encryption methods"
license = "MIT"
repository = "https://github.com/jdx/fnox"
readme = "README.md"
rust-version = "1.91.1"
[workspace.package]
version = "1.23.0"
edition = "2024"
authors = ["@jdx"]
license = "MIT"
repository = "https://github.com/jdx/fnox"
rust-version = "1.91.1"

Comment thread Cargo.toml Outdated
openssl-sys = { version = "0.9", features = ["vendored"] }
[package]
name = "fnox"
version = "1.23.0"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Once the version is centralized in [workspace.package], you can use version.workspace = true here to keep it in sync with other workspace members.

Suggested change
version = "1.23.0"
version.workspace = true

Comment thread crates/fnox-core/Cargo.toml Outdated
@@ -0,0 +1,85 @@
[package]
name = "fnox-core"
version = "1.23.0"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Consider using version.workspace = true here to ensure this crate's version stays in sync with the rest of the workspace.

Suggested change
version = "1.23.0"
version.workspace = true

Comment thread crates/fnox-core/Cargo.toml Outdated
Comment on lines +71 to +75
ctap-hid-fido2 = "3"

[target.'cfg(target_os = "linux")'.dependencies]
dbus = { version = "0.9", features = ["vendored"] }
openssl-sys = { version = "0.9", features = ["vendored"] }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The dependencies ctap-hid-fido2, dbus, and openssl-sys are defined with explicit versions here, which is inconsistent with the other dependencies using workspace = true. To centralize version management and follow the plan described in the PR summary, these should be moved to the [workspace.dependencies] section in the root Cargo.toml. You can then reference them here using workspace = true within the target-specific dependency blocks.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit afd8419. Configure here.

Comment thread src/main.rs
jdx added 3 commits May 2, 2026 12:50
Four fixes from PR review:

- crates/fnox-core/src/lib.rs: crate doc claimed `lease_backends` was
  binary-only; it actually lives in fnox-core. Move it out of the binary's
  module list in the doc comment.
- crates/fnox-core/src/library.rs: drop broken intra-doc links to
  `[commands]` / `[commands::get::*]` / `[commands::set::*]` (those modules
  live in the binary, not fnox-core, so rustdoc couldn't resolve them) and
  qualify the `secret_resolver::resolve_secret` link with `crate::`.
- Cargo.toml: hoist `version = "1.23.0"` into `[workspace.package]` and
  reference it from both `fnox` and `fnox-core` via `version.workspace = true`,
  matching how edition / authors / license / etc. are already inherited.
- Cargo.toml + crates/fnox-core/Cargo.toml: move the platform-specific
  `ctap-hid-fido2`, `dbus`, and `openssl-sys` deps into
  `[workspace.dependencies]` and reference them with `workspace = true` from
  the target-cfg blocks, matching the rest of the workspace dependency
  layout.

cargo build, cargo test --workspace, mise run lint, and cargo doc --no-deps
all still pass; the broken intra-doc-link warnings the reviewer flagged are
gone.
Real bug caught by Cursor Bugbot: the tracing env-filter
`format!("fnox={}", log_level)` only matches log targets from the binary
crate. After moving most modules into `fnox_core`, every `tracing::*` call
in providers, config, lease, http, etc. emits under `fnox_core::*` targets
and was silently dropped — `--verbose` no longer surfaced debug output and
warn-level operational messages were lost. Filter now covers both crates.

Audit of GitHub Actions workflows for the workspace move turned up
additional gaps that CI and the release pipeline weren't catching because
they only operated on the root crate:

- `.github/workflows/ci.yml`:
  - `cargo test` was only running 8 binary-crate tests; the 147 fnox-core
    tests were silently skipped. Switch to `cargo test --workspace`.
  - `cargo clippy -- -D warnings` only linted the binary; switch to
    `cargo clippy --workspace --all-targets -- -D warnings`.
  - `cargo msrv verify` only checked the binary; switch to `--workspace`.

- `mise-tasks/release-plz`:
  - `cargo set-version --package fnox` doesn't update `[workspace.package]`
    and would fail against `version.workspace = true`. Switch to
    `cargo set-version --workspace`, which bumps the inherited workspace
    version (and the path-dep pin in fnox's Cargo.toml) automatically.
  - `cargo publish -p fnox` would fail because fnox depends on fnox-core
    via path + version, and fnox-core needs to be on crates.io first.
    Publish fnox-core, then fnox.

- `crates/fnox-core/src/auth_prompt.rs` + `settings.rs`:
  Three pre-existing `assert_eq!(x, false)` clippy warnings in test code
  that the pre-refactor `cargo clippy -- -D warnings` never saw because it
  only checked the binary. Replace with `assert!(!x)` so the now-stricter
  workspace clippy check passes.

cargo build, cargo test --workspace, cargo clippy --workspace --all-targets
-- -D warnings, and mise run lint all pass.
cargo-msrv doesn't have a --workspace flag; the previous commit's change
broke the msrv job with `error: unexpected argument '--workspace' found`.
Revert to plain `cargo msrv verify`. The binary depends on fnox-core via
path, so verifying the binary's MSRV transitively verifies fnox-core too.
@jdx jdx merged commit f84bc0f into main May 2, 2026
16 checks passed
@jdx jdx deleted the extract-core-crate branch May 2, 2026 18:24
jdx pushed a commit that referenced this pull request May 2, 2026
### 🚜 Refactor

- extract providers and core types into fnox-core crate by
[@jdx](https://github.com/jdx) in
[#458](#458)

### 📚 Documentation

- prefix star count with ★ glyph and populate it on deploy by
[@jdx](https://github.com/jdx) in
[#447](#447)
- add favicons and web app manifest by [@jdx](https://github.com/jdx) in
[#448](#448)

### 🔍 Other Changes

- **(docs)** remove shrill.en.dev analytics script by
[@jdx](https://github.com/jdx) in
[#457](#457)
- **(release)** add musl Linux targets for Alpine compatibility by
[@jdx](https://github.com/jdx) in
[#452](#452)
- add plausible analytics by [@jdx](https://github.com/jdx) in
[#451](#451)
- bump hk to 1.44.3 by [@jdx](https://github.com/jdx) in
[#454](#454)

### 📦️ Dependency Updates

- update autofix-ci/action action to v1.3.4 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#455](#455)
- update apple-actions/import-codesign-certs action to v7 by
[@renovate[bot]](https://github.com/renovate[bot]) in
[#456](#456)
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