From 1416b0fc99d82a014e7a609d38058e2f8e76a65d Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Sun, 22 Feb 2026 18:37:54 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20karapace-cli=20=E2=80=94=2023=20command?= =?UTF-8?q?s,=20thin=20dispatcher,=20progress=20indicators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 23 commands, each in its own module under commands/ - Thin main.rs dispatcher with clap subcommand routing - Progress spinners (indicatif) and colored state output (console) - Environment resolution by env_id, short_id, name, or prefix - Structured JSON output (--json) on all query commands - --verbose/-v for debug, --trace for trace-level logging - KARAPACE_LOG env var for fine-grained log control - Exit codes: 0 success, 1 failure, 2 manifest error, 3 store error - Prerequisite check before runtime operations - Shell completions (bash/zsh/fish/elvish/powershell) and man page generation --- crates/karapace-cli/Cargo.toml | 37 + crates/karapace-cli/karapace-cli.cdx.json | 4614 +++++++++++++++++ crates/karapace-cli/src/commands/archive.rs | 14 + crates/karapace-cli/src/commands/build.rs | 57 + crates/karapace-cli/src/commands/commit.rs | 22 + .../karapace-cli/src/commands/completions.rs | 9 + crates/karapace-cli/src/commands/destroy.rs | 14 + crates/karapace-cli/src/commands/diff.rs | 26 + crates/karapace-cli/src/commands/doctor.rs | 255 + crates/karapace-cli/src/commands/enter.rs | 22 + crates/karapace-cli/src/commands/exec.rs | 19 + crates/karapace-cli/src/commands/freeze.rs | 14 + crates/karapace-cli/src/commands/gc.rs | 33 + crates/karapace-cli/src/commands/inspect.rs | 21 + crates/karapace-cli/src/commands/list.rs | 22 + crates/karapace-cli/src/commands/man_pages.rs | 26 + crates/karapace-cli/src/commands/migrate.rs | 100 + crates/karapace-cli/src/commands/mod.rs | 209 + crates/karapace-cli/src/commands/pull.rs | 44 + crates/karapace-cli/src/commands/push.rs | 46 + crates/karapace-cli/src/commands/rebuild.rs | 57 + crates/karapace-cli/src/commands/rename.rs | 16 + crates/karapace-cli/src/commands/restore.rs | 31 + crates/karapace-cli/src/commands/snapshots.rs | 39 + crates/karapace-cli/src/commands/stop.rs | 14 + .../karapace-cli/src/commands/verify_store.rs | 30 + crates/karapace-cli/src/main.rs | 302 ++ crates/karapace-cli/tests/cli_integration.rs | 310 ++ 28 files changed, 6403 insertions(+) create mode 100644 crates/karapace-cli/Cargo.toml create mode 100644 crates/karapace-cli/karapace-cli.cdx.json create mode 100644 crates/karapace-cli/src/commands/archive.rs create mode 100644 crates/karapace-cli/src/commands/build.rs create mode 100644 crates/karapace-cli/src/commands/commit.rs create mode 100644 crates/karapace-cli/src/commands/completions.rs create mode 100644 crates/karapace-cli/src/commands/destroy.rs create mode 100644 crates/karapace-cli/src/commands/diff.rs create mode 100644 crates/karapace-cli/src/commands/doctor.rs create mode 100644 crates/karapace-cli/src/commands/enter.rs create mode 100644 crates/karapace-cli/src/commands/exec.rs create mode 100644 crates/karapace-cli/src/commands/freeze.rs create mode 100644 crates/karapace-cli/src/commands/gc.rs create mode 100644 crates/karapace-cli/src/commands/inspect.rs create mode 100644 crates/karapace-cli/src/commands/list.rs create mode 100644 crates/karapace-cli/src/commands/man_pages.rs create mode 100644 crates/karapace-cli/src/commands/migrate.rs create mode 100644 crates/karapace-cli/src/commands/mod.rs create mode 100644 crates/karapace-cli/src/commands/pull.rs create mode 100644 crates/karapace-cli/src/commands/push.rs create mode 100644 crates/karapace-cli/src/commands/rebuild.rs create mode 100644 crates/karapace-cli/src/commands/rename.rs create mode 100644 crates/karapace-cli/src/commands/restore.rs create mode 100644 crates/karapace-cli/src/commands/snapshots.rs create mode 100644 crates/karapace-cli/src/commands/stop.rs create mode 100644 crates/karapace-cli/src/commands/verify_store.rs create mode 100644 crates/karapace-cli/src/main.rs create mode 100644 crates/karapace-cli/tests/cli_integration.rs diff --git a/crates/karapace-cli/Cargo.toml b/crates/karapace-cli/Cargo.toml new file mode 100644 index 0000000..219ae6a --- /dev/null +++ b/crates/karapace-cli/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "karapace-cli" +description = "CLI interface for Karapace deterministic environment engine" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[[bin]] +name = "karapace" +path = "src/main.rs" + +[dependencies] +clap.workspace = true +clap_complete.workspace = true +clap_mangen.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +serde.workspace = true +serde_json.workspace = true +tempfile.workspace = true +indicatif.workspace = true +console.workspace = true +libc.workspace = true +karapace-schema = { path = "../karapace-schema" } +karapace-core = { path = "../karapace-core" } +karapace-store = { path = "../karapace-store" } +karapace-runtime = { path = "../karapace-runtime" } +karapace-tui = { path = "../karapace-tui" } +karapace-remote = { path = "../karapace-remote" } + +[dev-dependencies] +tempfile.workspace = true +serde_json.workspace = true diff --git a/crates/karapace-cli/karapace-cli.cdx.json b/crates/karapace-cli/karapace-cli.cdx.json new file mode 100644 index 0000000..bb7c418 --- /dev/null +++ b/crates/karapace-cli/karapace-cli.cdx.json @@ -0,0 +1,4614 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.3", + "version": 1, + "serialNumber": "urn:uuid:b2ded34a-f578-41ac-88a1-bdef25251674", + "metadata": { + "timestamp": "2026-02-22T14:03:10.590776813Z", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cargo-cyclonedx", + "version": "0.5.5" + } + ], + "component": { + "type": "application", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-cli#0.1.0", + "name": "karapace-cli", + "version": "0.1.0", + "description": "CLI interface for Karapace deterministic environment engine", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-cli@0.1.0?download_url=file://.", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ], + "components": [ + { + "type": "application", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-cli#0.1.0 bin-target-0", + "name": "karapace", + "version": "0.1.0", + "purl": "pkg:cargo/karapace-cli@0.1.0?download_url=file://.#src/main.rs" + } + ] + } + }, + "components": [ + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-core#0.1.0", + "name": "karapace-core", + "version": "0.1.0", + "description": "Build engine, lifecycle state machine, drift control, and concurrency for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-core@0.1.0?download_url=file://../karapace-core", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-remote#0.1.0", + "name": "karapace-remote", + "version": "0.1.0", + "description": "Remote content-addressable store for Karapace environment sharing", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-remote@0.1.0?download_url=file://../karapace-remote", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-runtime#0.1.0", + "name": "karapace-runtime", + "version": "0.1.0", + "description": "Container runtime backends, image management, sandbox, and host integration for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-runtime@0.1.0?download_url=file://../karapace-runtime", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "name": "karapace-schema", + "version": "0.1.0", + "description": "Manifest parsing, normalization, identity hashing, and lock file for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-schema@0.1.0?download_url=file://../karapace-schema", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "name": "karapace-store", + "version": "0.1.0", + "description": "Content-addressable store, metadata, layers, GC, and integrity for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-store@0.1.0?download_url=file://../karapace-store", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-tui#0.1.0", + "name": "karapace-tui", + "version": "0.1.0", + "description": "Terminal UI for Karapace environment manager", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-tui@0.1.0?download_url=file://../karapace-tui", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.1", + "name": "adler2", + "version": "2.0.1", + "description": "A simple clean-room implementation of the Adler-32 checksum", + "scope": "required", + "licenses": [ + { + "expression": "0BSD OR MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/adler2@2.0.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/adler2/" + }, + { + "type": "vcs", + "url": "https://github.com/oyvindln/adler2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.4", + "name": "aho-corasick", + "version": "1.1.4", + "description": "Fast multiple substring searching.", + "scope": "required", + "licenses": [ + { + "expression": "Unlicense OR MIT" + } + ], + "purl": "pkg:cargo/aho-corasick@1.1.4", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/BurntSushi/aho-corasick" + }, + { + "type": "vcs", + "url": "https://github.com/BurntSushi/aho-corasick" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "name": "allocator-api2", + "version": "0.2.21", + "description": "Mirror of Rust's allocator API", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/allocator-api2@0.2.21", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/allocator-api2" + }, + { + "type": "website", + "url": "https://github.com/zakarumych/allocator-api2" + }, + { + "type": "vcs", + "url": "https://github.com/zakarumych/allocator-api2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.21", + "name": "anstream", + "version": "0.6.21", + "description": "IO stream adapters for writing colored text that will gracefully degrade according to your terminal's capabilities.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/anstream@0.6.21", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-cli/anstyle.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.7", + "name": "anstyle-parse", + "version": "0.2.7", + "description": "Parse ANSI Style Escapes", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/anstyle-parse@0.2.7", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-cli/anstyle.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.5", + "name": "anstyle-query", + "version": "1.1.5", + "description": "Look up colored console capabilities", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/anstyle-query@1.1.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-cli/anstyle.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.13", + "name": "anstyle", + "version": "1.0.13", + "description": "ANSI text styling", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/anstyle@1.0.13", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-cli/anstyle.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#arrayref@0.3.9", + "name": "arrayref", + "version": "0.3.9", + "description": "Macros to take array references of slices", + "scope": "required", + "licenses": [ + { + "expression": "BSD-2-Clause" + } + ], + "purl": "pkg:cargo/arrayref@0.3.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/arrayref" + }, + { + "type": "vcs", + "url": "https://github.com/droundy/arrayref" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.6", + "name": "arrayvec", + "version": "0.7.6", + "description": "A vector with fixed capacity, backed by an array (it can be stored on the stack too). Implements fixed capacity ArrayVec and ArrayString.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/arrayvec@0.7.6", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/arrayvec/" + }, + { + "type": "vcs", + "url": "https://github.com/bluss/arrayvec" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.5.0", + "name": "autocfg", + "version": "1.5.0", + "description": "Automatic cfg for Rust compiler features", + "scope": "excluded", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/autocfg@1.5.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/autocfg/" + }, + { + "type": "vcs", + "url": "https://github.com/cuviper/autocfg" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "name": "base64", + "version": "0.22.1", + "description": "encodes and decodes base64 as bytes or utf8", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/base64@0.22.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/base64" + }, + { + "type": "vcs", + "url": "https://github.com/marshallpierce/rust-base64" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "name": "bitflags", + "version": "2.11.0", + "description": "A macro to generate structures which behave like bitflags. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/bitflags@2.11.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/bitflags" + }, + { + "type": "website", + "url": "https://github.com/bitflags/bitflags" + }, + { + "type": "vcs", + "url": "https://github.com/bitflags/bitflags" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "name": "blake3", + "version": "1.8.3", + "description": "the BLAKE3 hash function", + "scope": "required", + "licenses": [ + { + "expression": "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception" + } + ], + "purl": "pkg:cargo/blake3@1.8.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/blake3" + }, + { + "type": "vcs", + "url": "https://github.com/BLAKE3-team/BLAKE3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bytes@1.11.1", + "name": "bytes", + "version": "1.11.1", + "description": "Types and traits for working with bytes", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/bytes@1.11.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/tokio-rs/bytes" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cassowary@0.3.0", + "name": "cassowary", + "version": "0.3.0", + "description": "A Rust implementation of the Cassowary linear constraint solving algorithm. The Cassowary algorithm is designed for naturally laying out user interfaces using linear constraints, like 'this button must line up with this text box'. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cassowary@0.3.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://dylanede.github.io/cassowary-rs" + }, + { + "type": "website", + "url": "https://github.com/dylanede/cassowary-rs" + }, + { + "type": "vcs", + "url": "https://github.com/dylanede/cassowary-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#castaway@0.2.4", + "name": "castaway", + "version": "0.2.4", + "description": "Safe, zero-cost downcasting for limited compile-time specialization.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/castaway@0.2.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/sagebind/castaway" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "name": "cc", + "version": "1.2.56", + "description": "A build-time dependency for Cargo build scripts to assist in invoking the native C compiler to compile native C code into a static archive to be linked into Rust code. ", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cc@1.2.56", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cc" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/cc-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "name": "cfg-if", + "version": "1.0.4", + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cfg-if@1.0.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/cfg-if" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cfg_aliases@0.2.1", + "name": "cfg_aliases", + "version": "0.2.1", + "description": "A tiny utility to help save you a lot of effort with long winded `#[cfg()]` checks.", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/cfg_aliases@0.2.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cfg_aliases" + }, + { + "type": "website", + "url": "https://github.com/katharostech/cfg_aliases" + }, + { + "type": "vcs", + "url": "https://github.com/katharostech/cfg_aliases" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "name": "chrono", + "version": "0.4.43", + "description": "Date and time library for Rust", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/chrono@0.4.43", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/chrono/" + }, + { + "type": "website", + "url": "https://github.com/chronotope/chrono" + }, + { + "type": "vcs", + "url": "https://github.com/chronotope/chrono" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#clap@4.5.60", + "name": "clap", + "version": "4.5.60", + "description": "A simple to use, efficient, and full-featured Command Line Argument Parser", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/clap@4.5.60", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/clap-rs/clap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.60", + "name": "clap_builder", + "version": "4.5.60", + "description": "A simple to use, efficient, and full-featured Command Line Argument Parser", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/clap_builder@4.5.60", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/clap-rs/clap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#clap_complete@4.5.66", + "name": "clap_complete", + "version": "4.5.66", + "description": "Generate shell completion scripts for your clap::Command", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/clap_complete@4.5.66", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/clap-rs/clap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.55", + "name": "clap_derive", + "version": "4.5.55", + "description": "Parse command line argument by defining a struct, derive crate.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/clap_derive@4.5.55", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/clap-rs/clap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#clap_lex@1.0.0", + "name": "clap_lex", + "version": "1.0.0", + "description": "Minimal, flexible command line parser", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/clap_lex@1.0.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/clap-rs/clap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#clap_mangen@0.2.31", + "name": "clap_mangen", + "version": "0.2.31", + "description": "A manpage generator for clap", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/clap_mangen@0.2.31", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/clap-rs/clap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.4", + "name": "colorchoice", + "version": "1.0.4", + "description": "Global override of color control", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/colorchoice@1.0.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-cli/anstyle.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#compact_str@0.8.1", + "name": "compact_str", + "version": "0.8.1", + "description": "A memory efficient string type that transparently stores strings on the stack, when possible", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/compact_str@0.8.1", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/ParkMyCar/compact_str" + }, + { + "type": "vcs", + "url": "https://github.com/ParkMyCar/compact_str" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#console@0.15.11", + "name": "console", + "version": "0.15.11", + "description": "A terminal and console abstraction for Rust", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/console@0.15.11", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/console" + }, + { + "type": "website", + "url": "https://github.com/console-rs/console" + }, + { + "type": "vcs", + "url": "https://github.com/console-rs/console" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#constant_time_eq@0.4.2", + "name": "constant_time_eq", + "version": "0.4.2", + "description": "Compares two equal-sized byte strings in constant time.", + "scope": "required", + "licenses": [ + { + "expression": "CC0-1.0 OR MIT-0 OR Apache-2.0" + } + ], + "purl": "pkg:cargo/constant_time_eq@0.4.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/constant_time_eq" + }, + { + "type": "vcs", + "url": "https://github.com/cesarb/constant_time_eq" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.17", + "name": "cpufeatures", + "version": "0.2.17", + "description": "Lightweight runtime CPU feature detection for aarch64, loongarch64, and x86/x86_64 targets, with no_std support and support for mobile targets including Android and iOS ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cpufeatures@0.2.17", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cpufeatures" + }, + { + "type": "vcs", + "url": "https://github.com/RustCrypto/utils" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0", + "name": "crc32fast", + "version": "1.5.0", + "description": "Fast, SIMD-accelerated CRC32 (IEEE) checksum computation", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/crc32fast@1.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/srijs/rust-crc32fast" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "name": "crossterm", + "version": "0.28.1", + "description": "A crossplatform terminal library for manipulating terminals.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/crossterm@0.28.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/crossterm/" + }, + { + "type": "vcs", + "url": "https://github.com/crossterm-rs/crossterm" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ctrlc@3.5.2", + "name": "ctrlc", + "version": "3.5.2", + "description": "Easy Ctrl-C handler for Rust projects", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ctrlc@3.5.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://detegr.github.io/doc/ctrlc" + }, + { + "type": "website", + "url": "https://github.com/Detegr/rust-ctrlc" + }, + { + "type": "vcs", + "url": "https://github.com/Detegr/rust-ctrlc.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#darling@0.23.0", + "name": "darling", + "version": "0.23.0", + "description": "A proc-macro library for reading attributes into structs when implementing custom derives. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/darling@0.23.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/darling/0.23.0" + }, + { + "type": "vcs", + "url": "https://github.com/TedDriggs/darling" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "name": "darling_core", + "version": "0.23.0", + "description": "Helper crate for proc-macro library for reading attributes into structs when implementing custom derives. Use https://crates.io/crates/darling in your code. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/darling_core@0.23.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/TedDriggs/darling" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#darling_macro@0.23.0", + "name": "darling_macro", + "version": "0.23.0", + "description": "Internal support for a proc-macro library for reading attributes into structs when implementing custom derives. Use https://crates.io/crates/darling in your code. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/darling_macro@0.23.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/TedDriggs/darling" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#either@1.15.0", + "name": "either", + "version": "1.15.0", + "description": "The enum `Either` with variants `Left` and `Right` is a general purpose sum type with two cases. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/either@1.15.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/either/1/" + }, + { + "type": "vcs", + "url": "https://github.com/rayon-rs/either" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "name": "equivalent", + "version": "1.0.2", + "description": "Traits for key comparison in maps.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/equivalent@1.0.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/indexmap-rs/equivalent" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "name": "errno", + "version": "0.3.14", + "description": "Cross-platform interface to the `errno` variable.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/errno@0.3.14", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/errno" + }, + { + "type": "vcs", + "url": "https://github.com/lambda-fairy/rust-errno" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0", + "name": "fastrand", + "version": "2.3.0", + "description": "A simple and fast random number generator", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/fastrand@2.3.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/smol-rs/fastrand" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#filetime@0.2.27", + "name": "filetime", + "version": "0.2.27", + "description": "Platform-agnostic accessors of timestamps in File metadata ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/filetime@0.2.27", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/filetime" + }, + { + "type": "website", + "url": "https://github.com/alexcrichton/filetime" + }, + { + "type": "vcs", + "url": "https://github.com/alexcrichton/filetime" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "name": "find-msvc-tools", + "version": "0.1.9", + "description": "Find windows-specific tools, read MSVC versions from the registry and from COM interfaces", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/find-msvc-tools@0.1.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/find-msvc-tools" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#flate2@1.1.9", + "name": "flate2", + "version": "1.1.9", + "description": "DEFLATE compression and decompression exposed as Read/BufRead/Write streams. Supports miniz_oxide and multiple zlib implementations. Supports zlib, gzip, and raw deflate streams. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/flate2@1.1.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/flate2" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/flate2-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/flate2-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.5", + "name": "foldhash", + "version": "0.1.5", + "description": "A fast, non-cryptographic, minimally DoS-resistant hashing algorithm.", + "scope": "required", + "licenses": [ + { + "expression": "Zlib" + } + ], + "purl": "pkg:cargo/foldhash@0.1.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/orlp/foldhash" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "name": "fs2", + "version": "0.4.3", + "description": "Cross-platform file locks and file duplication.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/fs2@0.4.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/fs2" + }, + { + "type": "vcs", + "url": "https://github.com/danburkert/fs2-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.17", + "name": "getrandom", + "version": "0.2.17", + "description": "A small cross-platform library for retrieving random data from system source", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/getrandom@0.2.17", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/getrandom" + }, + { + "type": "vcs", + "url": "https://github.com/rust-random/getrandom" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.1", + "name": "getrandom", + "version": "0.4.1", + "description": "A small cross-platform library for retrieving random data from system source", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/getrandom@0.4.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/getrandom" + }, + { + "type": "vcs", + "url": "https://github.com/rust-random/getrandom" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.5", + "name": "hashbrown", + "version": "0.15.5", + "description": "A Rust port of Google's SwissTable hash map", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/hashbrown@0.15.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/hashbrown" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "name": "hashbrown", + "version": "0.16.1", + "description": "A Rust port of Google's SwissTable hash map", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/hashbrown@0.16.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/hashbrown" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "name": "heck", + "version": "0.5.0", + "description": "heck is a case conversion library.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/heck@0.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/withoutboats/heck" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#http@1.4.0", + "name": "http", + "version": "1.4.0", + "description": "A set of types for representing HTTP requests and responses. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/http@1.4.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/http" + }, + { + "type": "vcs", + "url": "https://github.com/hyperium/http" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#httparse@1.10.1", + "name": "httparse", + "version": "1.10.1", + "description": "A tiny, safe, speedy, zero-copy HTTP/1.x parser.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/httparse@1.10.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/httparse" + }, + { + "type": "vcs", + "url": "https://github.com/seanmonstar/httparse" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.65", + "name": "iana-time-zone", + "version": "0.1.65", + "description": "get the IANA time zone for the current system", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/iana-time-zone@0.1.65", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/strawlab/iana-time-zone" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ident_case@1.0.1", + "name": "ident_case", + "version": "1.0.1", + "description": "Utility for applying case rules to Rust identifiers.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ident_case@1.0.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/ident_case/1.0.1" + }, + { + "type": "vcs", + "url": "https://github.com/TedDriggs/ident_case" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#indexmap@2.13.0", + "name": "indexmap", + "version": "2.13.0", + "description": "A hash table with consistent order and fast iteration.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/indexmap@2.13.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/indexmap/" + }, + { + "type": "vcs", + "url": "https://github.com/indexmap-rs/indexmap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#indicatif@0.17.11", + "name": "indicatif", + "version": "0.17.11", + "description": "A progress bar and cli reporting library for Rust", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/indicatif@0.17.11", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/indicatif" + }, + { + "type": "vcs", + "url": "https://github.com/console-rs/indicatif" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "name": "indoc", + "version": "2.0.7", + "description": "Indented document literals", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/indoc@2.0.7", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/indoc" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/indoc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#instability@0.3.11", + "name": "instability", + "version": "0.3.11", + "description": "Rust API stability attributes for the rest of us. A fork of the `stability` crate.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/instability@0.3.11", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/instability/" + }, + { + "type": "vcs", + "url": "https://github.com/ratatui/instability" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.2", + "name": "is_terminal_polyfill", + "version": "1.70.2", + "description": "Polyfill for `is_terminal` stdlib feature for use with older MSRVs", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/is_terminal_polyfill@1.70.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/polyfill-rs/is_terminal_polyfill" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "name": "itertools", + "version": "0.13.0", + "description": "Extra iterator adaptors, iterator methods, free functions, and macros.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/itertools@0.13.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/itertools/" + }, + { + "type": "vcs", + "url": "https://github.com/rust-itertools/itertools" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "name": "itoa", + "version": "1.0.17", + "description": "Fast integer primitive to string conversion", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/itoa@1.0.17", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/itoa" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/itoa" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.5.0", + "name": "lazy_static", + "version": "1.5.0", + "description": "A macro for declaring lazily evaluated statics in Rust.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/lazy_static@1.5.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/lazy_static" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang-nursery/lazy-static.rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "name": "libc", + "version": "0.2.180", + "description": "Raw FFI bindings to platform libraries like libc.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/libc@0.2.180", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/libc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.11.0", + "name": "linux-raw-sys", + "version": "0.11.0", + "description": "Generated bindings for Linux's userspace API", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/linux-raw-sys@0.11.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/linux-raw-sys" + }, + { + "type": "vcs", + "url": "https://github.com/sunfishcode/linux-raw-sys" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15", + "name": "linux-raw-sys", + "version": "0.4.15", + "description": "Generated bindings for Linux's userspace API", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/linux-raw-sys@0.4.15", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/linux-raw-sys" + }, + { + "type": "vcs", + "url": "https://github.com/sunfishcode/linux-raw-sys" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.14", + "name": "lock_api", + "version": "0.4.14", + "description": "Wrappers to create fully-featured Mutex and RwLock types. Compatible with no_std.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/lock_api@0.4.14", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Amanieu/parking_lot" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "name": "log", + "version": "0.4.29", + "description": "A lightweight logging facade for Rust ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/log@0.4.29", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/log" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/log" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lru@0.12.5", + "name": "lru", + "version": "0.12.5", + "description": "A LRU cache implementation", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/lru@0.12.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/lru/" + }, + { + "type": "website", + "url": "https://github.com/jeromefroe/lru-rs" + }, + { + "type": "vcs", + "url": "https://github.com/jeromefroe/lru-rs.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#matchers@0.2.0", + "name": "matchers", + "version": "0.2.0", + "description": "Regex matching on character and byte streams. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/matchers@0.2.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/matchers/" + }, + { + "type": "website", + "url": "https://github.com/hawkw/matchers" + }, + { + "type": "vcs", + "url": "https://github.com/hawkw/matchers" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0", + "name": "memchr", + "version": "2.8.0", + "description": "Provides extremely fast (uses SIMD on x86_64, aarch64 and wasm32) routines for 1, 2 or 3 byte search and single substring search. ", + "scope": "required", + "licenses": [ + { + "expression": "Unlicense OR MIT" + } + ], + "purl": "pkg:cargo/memchr@2.8.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/memchr/" + }, + { + "type": "website", + "url": "https://github.com/BurntSushi/memchr" + }, + { + "type": "vcs", + "url": "https://github.com/BurntSushi/memchr" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.9", + "name": "miniz_oxide", + "version": "0.8.9", + "description": "DEFLATE compression and decompression library rewritten in Rust based on miniz", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Zlib OR Apache-2.0" + } + ], + "purl": "pkg:cargo/miniz_oxide@0.8.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/miniz_oxide" + }, + { + "type": "website", + "url": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide" + }, + { + "type": "vcs", + "url": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "name": "mio", + "version": "1.1.1", + "description": "Lightweight non-blocking I/O.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/mio@1.1.1", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/tokio-rs/mio" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/mio" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#nix@0.31.1", + "name": "nix", + "version": "0.31.1", + "description": "Rust friendly bindings to *nix APIs", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/nix@0.31.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/nix-rust/nix" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#nu-ansi-term@0.50.3", + "name": "nu-ansi-term", + "version": "0.50.3", + "description": "Library for ANSI terminal colors and styles (bold, underline)", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/nu-ansi-term@0.50.3", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/nushell/nu-ansi-term" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "name": "num-traits", + "version": "0.2.19", + "description": "Numeric traits for generic mathematics", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/num-traits@0.2.19", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/num-traits" + }, + { + "type": "website", + "url": "https://github.com/rust-num/num-traits" + }, + { + "type": "vcs", + "url": "https://github.com/rust-num/num-traits" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#number_prefix@0.4.0", + "name": "number_prefix", + "version": "0.4.0", + "description": "Library for numeric prefixes (kilo, giga, kibi).", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/number_prefix@0.4.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/number_prefix" + }, + { + "type": "vcs", + "url": "https://github.com/ogham/rust-number-prefix" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "name": "once_cell", + "version": "1.21.3", + "description": "Single assignment cells and lazy values.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/once_cell@1.21.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/once_cell" + }, + { + "type": "vcs", + "url": "https://github.com/matklad/once_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.5", + "name": "parking_lot", + "version": "0.12.5", + "description": "More compact and efficient implementations of the standard synchronization primitives.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/parking_lot@0.12.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Amanieu/parking_lot" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12", + "name": "parking_lot_core", + "version": "0.9.12", + "description": "An advanced API for creating custom synchronization primitives.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/parking_lot_core@0.9.12", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Amanieu/parking_lot" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15", + "name": "paste", + "version": "1.0.15", + "description": "Macros for all your token pasting needs", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/paste@1.0.15", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/paste" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/paste" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "name": "percent-encoding", + "version": "2.3.2", + "description": "Percent encoding and decoding", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/percent-encoding@2.3.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/servo/rust-url/" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16", + "name": "pin-project-lite", + "version": "0.2.16", + "description": "A lightweight version of pin-project written with declarative macros. ", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/pin-project-lite@0.2.16", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/taiki-e/pin-project-lite" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.13.1", + "name": "portable-atomic", + "version": "1.13.1", + "description": "Portable atomic types including support for 128-bit atomics, atomic float, etc. ", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/portable-atomic@1.13.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/taiki-e/portable-atomic" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "name": "proc-macro2", + "version": "1.0.106", + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/proc-macro2@1.0.106", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/proc-macro2" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/proc-macro2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "name": "quote", + "version": "1.0.44", + "description": "Quasi-quoting macro quote!(...)", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/quote@1.0.44", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/quote/" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/quote" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ratatui@0.29.0", + "name": "ratatui", + "version": "0.29.0", + "description": "A library that's all about cooking up terminal user interfaces", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/ratatui@0.29.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/ratatui/latest/ratatui/" + }, + { + "type": "website", + "url": "https://ratatui.rs" + }, + { + "type": "vcs", + "url": "https://github.com/ratatui/ratatui" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.14", + "name": "regex-automata", + "version": "0.4.14", + "description": "Automata construction and matching using regular expressions.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/regex-automata@0.4.14", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/regex-automata" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/regex/tree/master/regex-automata" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/regex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.9", + "name": "regex-syntax", + "version": "0.8.9", + "description": "A regular expression parser.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/regex-syntax@0.8.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/regex-syntax" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/regex/tree/master/regex-syntax" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/regex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "name": "ring", + "version": "0.17.14", + "description": "An experiment.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 AND ISC" + } + ], + "purl": "pkg:cargo/ring@0.17.14", + "externalReferences": [ + { + "type": "other", + "url": "ring_core_0_17_14_" + }, + { + "type": "vcs", + "url": "https://github.com/briansmith/ring" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#roff@0.2.2", + "name": "roff", + "version": "0.2.2", + "description": "ROFF (man page format) generation library", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/roff@0.2.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/roff" + }, + { + "type": "website", + "url": "https://github.com/rust-cli/roff-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-cli/roff-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.44", + "name": "rustix", + "version": "0.38.44", + "description": "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/rustix@0.38.44", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustix" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/rustix" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3", + "name": "rustix", + "version": "1.1.3", + "description": "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/rustix@1.1.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustix" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/rustix" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "name": "rustls-pki-types", + "version": "1.14.0", + "description": "Shared types for the rustls PKI ecosystem", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/rustls-pki-types@1.14.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustls-pki-types" + }, + { + "type": "website", + "url": "https://github.com/rustls/pki-types" + }, + { + "type": "vcs", + "url": "https://github.com/rustls/pki-types" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-webpki@0.103.9", + "name": "rustls-webpki", + "version": "0.103.9", + "description": "Web PKI X.509 Certificate Verification.", + "scope": "required", + "licenses": [ + { + "expression": "ISC" + } + ], + "purl": "pkg:cargo/rustls-webpki@0.103.9", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rustls/webpki" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustls@0.23.36", + "name": "rustls", + "version": "0.23.36", + "description": "Rustls is a modern TLS library written in Rust.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR ISC OR MIT" + } + ], + "purl": "pkg:cargo/rustls@0.23.36", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/rustls/rustls" + }, + { + "type": "vcs", + "url": "https://github.com/rustls/rustls" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "name": "rustversion", + "version": "1.0.22", + "description": "Conditional compilation according to rustc compiler version", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/rustversion@1.0.22", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustversion" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/rustversion" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.23", + "name": "ryu", + "version": "1.0.23", + "description": "Fast floating point to string conversion", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSL-1.0" + } + ], + "purl": "pkg:cargo/ryu@1.0.23", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/ryu" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/ryu" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0", + "name": "scopeguard", + "version": "1.2.0", + "description": "A RAII scope guard that will run a given closure when it goes out of scope, even if the code between panics (assuming unwinding panic). Defines the macros `defer!`, `defer_on_unwind!`, `defer_on_success!` as shorthands for guards with one of the implemented strategies. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/scopeguard@1.2.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/scopeguard/" + }, + { + "type": "vcs", + "url": "https://github.com/bluss/scopeguard" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "name": "serde", + "version": "1.0.228", + "description": "A generic serialization/deserialization framework", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "name": "serde_core", + "version": "1.0.228", + "description": "Serde traits only, with no support for derive -- use the `serde` crate instead", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_core@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde_core" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228", + "name": "serde_derive", + "version": "1.0.228", + "description": "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_derive@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://serde.rs/derive.html" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "name": "serde_json", + "version": "1.0.149", + "description": "A JSON serialization file format", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_json@1.0.149", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde_json" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/json" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "name": "serde_spanned", + "version": "0.6.9", + "description": "Serde-compatible spanned Value", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_spanned@0.6.9", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#sharded-slab@0.1.7", + "name": "sharded-slab", + "version": "0.1.7", + "description": "A lock-free concurrent slab. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/sharded-slab@0.1.7", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/sharded-slab/" + }, + { + "type": "website", + "url": "https://github.com/hawkw/sharded-slab" + }, + { + "type": "vcs", + "url": "https://github.com/hawkw/sharded-slab" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0", + "name": "shlex", + "version": "1.3.0", + "description": "Split a string into shell words, like Python's shlex.", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/shlex@1.3.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/comex/rust-shlex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-mio@0.2.5", + "name": "signal-hook-mio", + "version": "0.2.5", + "description": "MIO support for signal-hook", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/signal-hook-mio@0.2.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/signal-hook-mio" + }, + { + "type": "vcs", + "url": "https://github.com/vorner/signal-hook" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.8", + "name": "signal-hook-registry", + "version": "1.4.8", + "description": "Backend crate for signal-hook", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/signal-hook-registry@1.4.8", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/signal-hook-registry" + }, + { + "type": "vcs", + "url": "https://github.com/vorner/signal-hook" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18", + "name": "signal-hook", + "version": "0.3.18", + "description": "Unix signal handling", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/signal-hook@0.3.18", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/signal-hook" + }, + { + "type": "vcs", + "url": "https://github.com/vorner/signal-hook" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.8", + "name": "simd-adler32", + "version": "0.3.8", + "description": "A SIMD-accelerated Adler-32 hash algorithm implementation.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/simd-adler32@0.3.8", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/mcountryman/simd-adler32" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "name": "smallvec", + "version": "1.15.1", + "description": "'Small vector' optimization: store up to a small number of items on the stack", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/smallvec@1.15.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/smallvec/" + }, + { + "type": "vcs", + "url": "https://github.com/servo/rust-smallvec" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0", + "name": "static_assertions", + "version": "1.1.0", + "description": "Compile-time assertions to ensure that invariants are met.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/static_assertions@1.1.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/static_assertions/" + }, + { + "type": "website", + "url": "https://github.com/nvzqz/static-assertions-rs" + }, + { + "type": "vcs", + "url": "https://github.com/nvzqz/static-assertions-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1", + "name": "strsim", + "version": "0.11.1", + "description": "Implementations of string similarity metrics. Includes Hamming, Levenshtein, OSA, Damerau-Levenshtein, Jaro, Jaro-Winkler, and Sørensen-Dice. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strsim@0.11.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strsim/" + }, + { + "type": "website", + "url": "https://github.com/rapidfuzz/strsim-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rapidfuzz/strsim-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strum@0.26.3", + "name": "strum", + "version": "0.26.3", + "description": "Helpful macros for working with enums and strings", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strum@0.26.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strum" + }, + { + "type": "website", + "url": "https://github.com/Peternator7/strum" + }, + { + "type": "vcs", + "url": "https://github.com/Peternator7/strum" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.26.4", + "name": "strum_macros", + "version": "0.26.4", + "description": "Helpful macros for working with enums and strings", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strum_macros@0.26.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strum" + }, + { + "type": "website", + "url": "https://github.com/Peternator7/strum" + }, + { + "type": "vcs", + "url": "https://github.com/Peternator7/strum" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1", + "name": "subtle", + "version": "2.6.1", + "description": "Pure-Rust traits and utilities for constant-time cryptographic implementations.", + "scope": "required", + "licenses": [ + { + "expression": "BSD-3-Clause" + } + ], + "purl": "pkg:cargo/subtle@2.6.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/subtle" + }, + { + "type": "website", + "url": "https://dalek.rs/" + }, + { + "type": "vcs", + "url": "https://github.com/dalek-cryptography/subtle" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117", + "name": "syn", + "version": "2.0.117", + "description": "Parser for Rust source code", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/syn@2.0.117", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/syn" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/syn" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tar@0.4.44", + "name": "tar", + "version": "0.4.44", + "description": "A Rust implementation of a TAR file reader and writer. This library does not currently handle compression, but it is abstract over all I/O readers and writers. Additionally, great lengths are taken to ensure that the entire contents are never required to be entirely resident in memory all at once. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/tar@0.4.44", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/tar" + }, + { + "type": "website", + "url": "https://github.com/alexcrichton/tar-rs" + }, + { + "type": "vcs", + "url": "https://github.com/alexcrichton/tar-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "name": "tempfile", + "version": "3.25.0", + "description": "A library for managing temporary files and directories.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/tempfile@3.25.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/tempfile" + }, + { + "type": "website", + "url": "https://stebalien.com/projects/tempfile-rs/" + }, + { + "type": "vcs", + "url": "https://github.com/Stebalien/tempfile" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.18", + "name": "thiserror-impl", + "version": "2.0.18", + "description": "Implementation detail of the `thiserror` crate", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/thiserror-impl@2.0.18", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/dtolnay/thiserror" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "name": "thiserror", + "version": "2.0.18", + "description": "derive(Error)", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/thiserror@2.0.18", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/thiserror" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/thiserror" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#thread_local@1.1.9", + "name": "thread_local", + "version": "1.1.9", + "description": "Per-object thread-local storage", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/thread_local@1.1.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/thread_local/" + }, + { + "type": "vcs", + "url": "https://github.com/Amanieu/thread_local-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml@0.8.23", + "name": "toml", + "version": "0.8.23", + "description": "A native Rust encoder and decoder of TOML-formatted files and streams. Provides implementations of the standard Serialize/Deserialize traits for TOML data to facilitate deserializing and serializing Rust structures. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml@0.8.23", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "name": "toml_datetime", + "version": "0.6.11", + "description": "A TOML-compatible datetime type", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml_datetime@0.6.11", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.27", + "name": "toml_edit", + "version": "0.22.27", + "description": "Yet another format-preserving TOML parser.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml_edit@0.22.27", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml_write@0.1.2", + "name": "toml_write", + "version": "0.1.2", + "description": "A low-level interface for writing out TOML ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml_write@0.1.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.31", + "name": "tracing-attributes", + "version": "0.1.31", + "description": "Procedural macro attributes for automatically instrumenting functions. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing-attributes@0.1.31", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36", + "name": "tracing-core", + "version": "0.1.36", + "description": "Core primitives for application-level tracing. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing-core@0.1.36", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-log@0.2.0", + "name": "tracing-log", + "version": "0.2.0", + "description": "Provides compatibility between `tracing` and the `log` crate. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing-log@0.2.0", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-subscriber@0.3.22", + "name": "tracing-subscriber", + "version": "0.3.22", + "description": "Utilities for implementing and composing `tracing` subscribers. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing-subscriber@0.3.22", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "name": "tracing", + "version": "0.1.44", + "description": "Application-level tracing for Rust. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing@0.1.44", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24", + "name": "unicode-ident", + "version": "1.0.24", + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31", + "scope": "required", + "licenses": [ + { + "expression": "(MIT OR Apache-2.0) AND Unicode-3.0" + } + ], + "purl": "pkg:cargo/unicode-ident@1.0.24", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/unicode-ident" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/unicode-ident" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "name": "unicode-segmentation", + "version": "1.12.0", + "description": "This crate provides Grapheme Cluster, Word and Sentence boundaries according to Unicode Standard Annex #29 rules. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-segmentation@1.12.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/unicode-rs/unicode-segmentation" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-rs/unicode-segmentation" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-truncate@1.1.0", + "name": "unicode-truncate", + "version": "1.1.0", + "description": "Unicode-aware algorithm to pad or truncate `str` in terms of displayed width. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-truncate@1.1.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/Aetf/unicode-truncate" + }, + { + "type": "vcs", + "url": "https://github.com/Aetf/unicode-truncate" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14", + "name": "unicode-width", + "version": "0.1.14", + "description": "Determine displayed width of `char` and `str` types according to Unicode Standard Annex #11 rules. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-width@0.1.14", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/unicode-rs/unicode-width" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-rs/unicode-width" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0", + "name": "unicode-width", + "version": "0.2.0", + "description": "Determine displayed width of `char` and `str` types according to Unicode Standard Annex #11 rules. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-width@0.2.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/unicode-rs/unicode-width" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-rs/unicode-width" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0", + "name": "untrusted", + "version": "0.9.0", + "description": "Safe, fast, zero-panic, zero-crashing, zero-allocation parsing of untrusted inputs in Rust.", + "scope": "required", + "licenses": [ + { + "expression": "ISC" + } + ], + "purl": "pkg:cargo/untrusted@0.9.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://briansmith.org/rustdoc/untrusted/" + }, + { + "type": "vcs", + "url": "https://github.com/briansmith/untrusted" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ureq-proto@0.5.3", + "name": "ureq-proto", + "version": "0.5.3", + "description": "ureq support crate", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ureq-proto@0.5.3", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/algesten/ureq-proto" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ureq@3.2.0", + "name": "ureq", + "version": "3.2.0", + "description": "Simple, safe HTTP client", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ureq@3.2.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/algesten/ureq" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6", + "name": "utf-8", + "version": "0.7.6", + "description": "Incremental, zero-copy UTF-8 decoding with error handling", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/utf-8@0.7.6", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/SimonSapin/rust-utf8" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2", + "name": "utf8parse", + "version": "0.2.2", + "description": "Table-driven UTF-8 parser", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/utf8parse@0.2.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/utf8parse/" + }, + { + "type": "vcs", + "url": "https://github.com/alacritty/vte" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#webpki-roots@1.0.6", + "name": "webpki-roots", + "version": "1.0.6", + "description": "Mozilla's CA root certificates for use with webpki", + "scope": "required", + "licenses": [ + { + "expression": "CDLA-Permissive-2.0" + } + ], + "purl": "pkg:cargo/webpki-roots@1.0.6", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/rustls/webpki-roots" + }, + { + "type": "vcs", + "url": "https://github.com/rustls/webpki-roots" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.14", + "name": "winnow", + "version": "0.7.14", + "description": "A byte-oriented, zero-copy, parser combinators library", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/winnow@0.7.14", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/winnow-rs/winnow" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#xattr@1.6.1", + "name": "xattr", + "version": "1.6.1", + "description": "unix extended filesystem attributes", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/xattr@1.6.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/xattr" + }, + { + "type": "vcs", + "url": "https://github.com/Stebalien/xattr" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2", + "name": "zeroize", + "version": "1.8.2", + "description": "Securely clear secrets from memory with a simple trait built on stable Rust primitives which guarantee memory is zeroed using an operation will not be 'optimized away' by the compiler. Uses a portable pure Rust implementation that works everywhere, even WASM! ", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/zeroize@1.8.2", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/RustCrypto/utils/tree/master/zeroize" + }, + { + "type": "vcs", + "url": "https://github.com/RustCrypto/utils" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.21", + "name": "zmij", + "version": "1.0.21", + "description": "A double-to-string conversion algorithm based on Schubfach and yy", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/zmij@1.0.21", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/zmij" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/zmij" + } + ] + } + ], + "dependencies": [ + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-cli#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#clap@4.5.60", + "registry+https://github.com/rust-lang/crates.io-index#clap_complete@4.5.66", + "registry+https://github.com/rust-lang/crates.io-index#clap_mangen@0.2.31", + "registry+https://github.com/rust-lang/crates.io-index#console@0.15.11", + "registry+https://github.com/rust-lang/crates.io-index#indicatif@0.17.11", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-core#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-remote#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-runtime#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-tui#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "registry+https://github.com/rust-lang/crates.io-index#tracing-subscriber@0.3.22" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-core#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "registry+https://github.com/rust-lang/crates.io-index#ctrlc@3.5.2", + "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-remote#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-runtime#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-remote#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "registry+https://github.com/rust-lang/crates.io-index#ureq@3.2.0" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-runtime#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#toml@0.8.23" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tar@0.4.44", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-tui#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-core#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#ratatui@0.29.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.21", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.13", + "registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.7", + "registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.5", + "registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.2", + "registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.7", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.5", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.13", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#arrayref@0.3.9", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.6", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.5.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#arrayref@0.3.9", + "registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#constant_time_eq@0.4.2", + "registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.17" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bytes@1.11.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cassowary@0.3.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#castaway@0.2.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cfg_aliases@0.2.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.65", + "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#clap@4.5.60", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.60", + "registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.55" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.60", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.21", + "registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.13", + "registry+https://github.com/rust-lang/crates.io-index#clap_lex@1.0.0", + "registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#clap_complete@4.5.66", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#clap@4.5.60" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.55", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#clap_lex@1.0.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#clap_mangen@0.2.31", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#clap@4.5.60", + "registry+https://github.com/rust-lang/crates.io-index#roff@0.2.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.4", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#compact_str@0.8.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#castaway@0.2.4", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.23", + "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#console@0.15.11", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#constant_time_eq@0.4.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.17", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.5", + "registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.44", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook-mio@0.2.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ctrlc@3.5.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#nix@0.31.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#darling@0.23.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "registry+https://github.com/rust-lang/crates.io-index#darling_macro@0.23.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#ident_case@1.0.1", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#darling_macro@0.23.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#either@1.15.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#filetime@0.2.27", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#flate2@1.1.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.9" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.5", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.17", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#http@1.4.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bytes@1.11.1", + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#httparse@1.10.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.65", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ident_case@1.0.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#indexmap@2.13.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#indicatif@0.17.11", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#console@0.15.11", + "registry+https://github.com/rust-lang/crates.io-index#number_prefix@0.4.0", + "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.13.1", + "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#instability@0.3.11", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#darling@0.23.0", + "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#either@1.15.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.5.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.11.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lru@0.12.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#matchers@0.2.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.14" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.1", + "registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.8" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#nix@0.31.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#cfg_aliases@0.2.1", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#nu-ansi-term@0.50.3", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.5.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#number_prefix@0.4.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.14", + "registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.13.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ratatui@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#cassowary@0.3.0", + "registry+https://github.com/rust-lang/crates.io-index#compact_str@0.8.1", + "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "registry+https://github.com/rust-lang/crates.io-index#instability@0.3.11", + "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "registry+https://github.com/rust-lang/crates.io-index#lru@0.12.5", + "registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15", + "registry+https://github.com/rust-lang/crates.io-index#strum@0.26.3", + "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-truncate@1.1.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.4", + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0", + "registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.9" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.9", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.17", + "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#roff@0.2.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.11.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-webpki@0.103.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustls@0.23.36", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "registry+https://github.com/rust-lang/crates.io-index#rustls-webpki@0.103.9", + "registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1", + "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.23", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0", + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.21" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#sharded-slab@0.1.7", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.5.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-mio@0.2.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.8", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.8" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.8", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strum@0.26.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.26.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.26.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tar@0.4.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#filetime@0.2.27", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#xattr@1.6.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0", + "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.1", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.18", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.18" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#thread_local@1.1.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml@0.8.23", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.27" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.27", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#indexmap@2.13.0", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "registry+https://github.com/rust-lang/crates.io-index#toml_write@0.1.2", + "registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.14" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml_write@0.1.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.31", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-log@0.2.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-subscriber@0.3.22", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#matchers@0.2.0", + "registry+https://github.com/rust-lang/crates.io-index#nu-ansi-term@0.50.3", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.14", + "registry+https://github.com/rust-lang/crates.io-index#sharded-slab@0.1.7", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "registry+https://github.com/rust-lang/crates.io-index#thread_local@1.1.9", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36", + "registry+https://github.com/rust-lang/crates.io-index#tracing-log@0.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16", + "registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.31", + "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-truncate@1.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ureq-proto@0.5.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "registry+https://github.com/rust-lang/crates.io-index#http@1.4.0", + "registry+https://github.com/rust-lang/crates.io-index#httparse@1.10.1", + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ureq@3.2.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "registry+https://github.com/rust-lang/crates.io-index#flate2@1.1.9", + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "registry+https://github.com/rust-lang/crates.io-index#rustls@0.23.36", + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "registry+https://github.com/rust-lang/crates.io-index#ureq-proto@0.5.3", + "registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#webpki-roots@1.0.6" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#webpki-roots@1.0.6", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#xattr@1.6.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.21", + "dependsOn": [] + } + ] +} \ No newline at end of file diff --git a/crates/karapace-cli/src/commands/archive.rs b/crates/karapace-cli/src/commands/archive.rs new file mode 100644 index 0000000..f36dff8 --- /dev/null +++ b/crates/karapace-cli/src/commands/archive.rs @@ -0,0 +1,14 @@ +use super::{resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, env_id: &str) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + engine.archive(&resolved).map_err(|e| e.to_string())?; + println!("archived environment {env_id}"); + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/build.rs b/crates/karapace-cli/src/commands/build.rs new file mode 100644 index 0000000..2f19bba --- /dev/null +++ b/crates/karapace-cli/src/commands/build.rs @@ -0,0 +1,57 @@ +use super::{json_pretty, spin_fail, spin_ok, spinner, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run( + engine: &Engine, + store_path: &Path, + manifest: &Path, + name: Option<&str>, + json: bool, +) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let pb = if json { + None + } else { + Some(spinner("building environment...")) + }; + let result = match engine.build(manifest) { + Ok(r) => { + if let Some(ref pb) = pb { + spin_ok(pb, "environment built"); + } + r + } + Err(e) => { + if let Some(ref pb) = pb { + spin_fail(pb, "build failed"); + } + return Err(e.to_string()); + } + }; + if let Some(n) = name { + engine + .set_name(&result.identity.env_id, Some(n.to_owned())) + .map_err(|e| e.to_string())?; + } + if json { + let payload = serde_json::json!({ + "env_id": result.identity.env_id, + "short_id": result.identity.short_id, + "name": name, + "status": "built" + }); + println!("{}", json_pretty(&payload)?); + } else { + if let Some(n) = name { + println!("built environment '{}' ({})", n, result.identity.short_id); + } else { + println!("built environment {}", result.identity.short_id); + } + println!("env_id: {}", result.identity.env_id); + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/commit.rs b/crates/karapace-cli/src/commands/commit.rs new file mode 100644 index 0000000..be378ec --- /dev/null +++ b/crates/karapace-cli/src/commands/commit.rs @@ -0,0 +1,22 @@ +use super::{json_pretty, resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, env_id: &str, json: bool) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + let tar_hash = engine.commit(&resolved).map_err(|e| e.to_string())?; + if json { + let payload = serde_json::json!({ + "env_id": resolved, + "snapshot_hash": tar_hash, + }); + println!("{}", json_pretty(&payload)?); + } else { + println!("committed snapshot {tar_hash} for {env_id}"); + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/completions.rs b/crates/karapace-cli/src/commands/completions.rs new file mode 100644 index 0000000..af086fb --- /dev/null +++ b/crates/karapace-cli/src/commands/completions.rs @@ -0,0 +1,9 @@ +use super::EXIT_SUCCESS; +use clap::CommandFactory; +use clap_complete::Shell; + +#[allow(clippy::unnecessary_wraps)] +pub fn run(shell: Shell) -> Result { + clap_complete::generate(shell, &mut C::command(), "karapace", &mut std::io::stdout()); + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/destroy.rs b/crates/karapace-cli/src/commands/destroy.rs new file mode 100644 index 0000000..695baeb --- /dev/null +++ b/crates/karapace-cli/src/commands/destroy.rs @@ -0,0 +1,14 @@ +use super::{resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, env_id: &str) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + engine.destroy(&resolved).map_err(|e| e.to_string())?; + println!("destroyed environment {env_id}"); + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/diff.rs b/crates/karapace-cli/src/commands/diff.rs new file mode 100644 index 0000000..2dd3c0b --- /dev/null +++ b/crates/karapace-cli/src/commands/diff.rs @@ -0,0 +1,26 @@ +use super::{json_pretty, resolve_env_id, EXIT_SUCCESS}; +use karapace_core::Engine; + +pub fn run(engine: &Engine, env_id: &str, json: bool) -> Result { + let resolved = resolve_env_id(engine, env_id)?; + let report = + karapace_core::diff_overlay(engine.store_layout(), &resolved).map_err(|e| e.to_string())?; + + if json { + println!("{}", json_pretty(&report)?); + } else if report.has_drift { + println!("drift detected in environment {env_id}:"); + for f in &report.added { + println!(" + {f}"); + } + for f in &report.modified { + println!(" ~ {f}"); + } + for f in &report.removed { + println!(" - {f}"); + } + } else { + println!("no drift detected in environment {env_id}"); + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/doctor.rs b/crates/karapace-cli/src/commands/doctor.rs new file mode 100644 index 0000000..7426e62 --- /dev/null +++ b/crates/karapace-cli/src/commands/doctor.rs @@ -0,0 +1,255 @@ +use super::{EXIT_FAILURE, EXIT_SUCCESS}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(store_path: &Path, json_output: bool) -> Result { + let mut checks: Vec = Vec::new(); + let mut all_pass = true; + + check_prereqs(&mut checks, &mut all_pass); + + let layout = StoreLayout::new(store_path); + if store_path.join("store").exists() { + checks.push(Check::pass("store_exists", "Store directory exists")); + check_store(&layout, &mut checks, &mut all_pass); + check_disk_space(store_path, &mut checks); + } else { + checks.push(Check::info( + "store_exists", + "Store not initialized (will be created on first build)", + )); + } + + print_results(&checks, all_pass, json_output) +} + +fn check_prereqs(checks: &mut Vec, all_pass: &mut bool) { + let missing = karapace_runtime::check_namespace_prereqs(); + if missing.is_empty() { + checks.push(Check::pass( + "runtime_prereqs", + "Runtime prerequisites satisfied", + )); + } else { + *all_pass = false; + checks.push(Check::fail( + "runtime_prereqs", + &format!( + "Missing prerequisites: {}", + karapace_runtime::format_missing(&missing) + ), + )); + } +} + +fn check_store(layout: &StoreLayout, checks: &mut Vec, all_pass: &mut bool) { + // Version + match layout.initialize() { + Ok(()) => checks.push(Check::pass("store_version", "Store format version valid")), + Err(e) => { + *all_pass = false; + checks.push(Check::fail( + "store_version", + &format!("Store version check failed: {e}"), + )); + } + } + + // Integrity + match karapace_store::verify_store_integrity(layout) { + Ok(report) if report.failed.is_empty() => { + checks.push(Check::pass( + "store_integrity", + &format!("Store integrity OK ({} objects checked)", report.checked), + )); + } + Ok(report) => { + *all_pass = false; + checks.push(Check::fail( + "store_integrity", + &format!( + "{} of {} objects corrupted", + report.failed.len(), + report.checked + ), + )); + } + Err(e) => { + *all_pass = false; + checks.push(Check::fail( + "store_integrity", + &format!("Integrity check failed: {e}"), + )); + } + } + + // WAL + let wal = karapace_store::WriteAheadLog::new(layout); + match wal.list_incomplete() { + Ok(entries) if entries.is_empty() => { + checks.push(Check::pass( + "wal_clean", + "WAL is clean (no incomplete entries)", + )); + } + Ok(entries) => { + checks.push(Check::warn( + "wal_clean", + &format!( + "WAL has {} incomplete entries (will recover on next start)", + entries.len() + ), + )); + } + Err(e) => checks.push(Check::warn("wal_clean", &format!("Cannot read WAL: {e}"))), + } + + // Lock + match karapace_core::StoreLock::try_acquire(&layout.lock_file()) { + Ok(Some(_)) => checks.push(Check::pass("store_lock", "Store lock is free")), + Ok(None) => checks.push(Check::warn( + "store_lock", + "Store lock is held by another process", + )), + Err(e) => { + *all_pass = false; + checks.push(Check::fail( + "store_lock", + &format!("Cannot check store lock: {e}"), + )); + } + } + + // Environments + let meta_store = karapace_store::MetadataStore::new(layout.clone()); + match meta_store.list() { + Ok(envs) => { + let running = envs + .iter() + .filter(|e| e.state == karapace_store::EnvState::Running) + .count(); + checks.push(Check::info( + "environments", + &format!("{} environments ({running} running)", envs.len()), + )); + } + Err(e) => checks.push(Check::warn( + "environments", + &format!("Cannot list environments: {e}"), + )), + } +} + +fn print_results(checks: &[Check], all_pass: bool, json_output: bool) -> Result { + if json_output { + let json = serde_json::json!({ + "healthy": all_pass, + "checks": checks.iter().map(|c| serde_json::json!({ + "name": c.name, + "status": c.status, + "message": c.message, + })).collect::>(), + }); + println!( + "{}", + serde_json::to_string_pretty(&json).map_err(|e| e.to_string())? + ); + } else { + println!("Karapace Doctor\n"); + for check in checks { + let icon = match check.status.as_str() { + "pass" => "✓", + "fail" => "✗", + "warn" => "⚠", + _ => "ℹ", + }; + println!(" {icon} {}", check.message); + } + println!(); + if all_pass { + println!("All checks passed."); + } else { + println!("Some checks failed. See above for details."); + } + } + Ok(if all_pass { EXIT_SUCCESS } else { EXIT_FAILURE }) +} + +struct Check { + name: String, + status: String, + message: String, +} + +impl Check { + fn pass(name: &str, message: &str) -> Self { + Self { + name: name.to_owned(), + status: "pass".to_owned(), + message: message.to_owned(), + } + } + + fn fail(name: &str, message: &str) -> Self { + Self { + name: name.to_owned(), + status: "fail".to_owned(), + message: message.to_owned(), + } + } + + fn warn(name: &str, message: &str) -> Self { + Self { + name: name.to_owned(), + status: "warn".to_owned(), + message: message.to_owned(), + } + } + + fn info(name: &str, message: &str) -> Self { + Self { + name: name.to_owned(), + status: "info".to_owned(), + message: message.to_owned(), + } + } +} + +fn check_disk_space(store_path: &Path, checks: &mut Vec) { + let Ok(c_path) = std::ffi::CString::new(store_path.to_string_lossy().as_bytes()) else { + return; + }; + + // SAFETY: zeroed statvfs is a valid initial state for the struct. + #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)] + let mut stat: libc::statvfs = unsafe { std::mem::zeroed() }; + // SAFETY: statvfs with a valid, NUL-terminated path and a properly + // zeroed output struct is well-defined. The struct is stack-allocated + // and only read after the call succeeds (ret == 0). + #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)] + let ret = unsafe { libc::statvfs(c_path.as_ptr(), &raw mut stat) }; + if ret != 0 { + return; + } + + let avail_bytes = stat.f_bavail * stat.f_frsize; + let avail_mb = avail_bytes / (1024 * 1024); + + if avail_mb < 100 { + checks.push(Check::fail( + "disk_space", + &format!("Low disk space: {avail_mb} MB available"), + )); + } else if avail_mb < 1024 { + checks.push(Check::warn( + "disk_space", + &format!("Disk space: {avail_mb} MB available (consider freeing space)"), + )); + } else { + let free_gb = avail_mb / 1024; + checks.push(Check::pass( + "disk_space", + &format!("Disk space: {free_gb} GB available"), + )); + } +} diff --git a/crates/karapace-cli/src/commands/enter.rs b/crates/karapace-cli/src/commands/enter.rs new file mode 100644 index 0000000..7bc67bb --- /dev/null +++ b/crates/karapace-cli/src/commands/enter.rs @@ -0,0 +1,22 @@ +use super::{resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run( + engine: &Engine, + store_path: &Path, + env_id: &str, + command: &[String], +) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + if command.is_empty() { + engine.enter(&resolved).map_err(|e| e.to_string())?; + } else { + engine.exec(&resolved, command).map_err(|e| e.to_string())?; + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/exec.rs b/crates/karapace-cli/src/commands/exec.rs new file mode 100644 index 0000000..24e7ff5 --- /dev/null +++ b/crates/karapace-cli/src/commands/exec.rs @@ -0,0 +1,19 @@ +use super::{resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run( + engine: &Engine, + store_path: &Path, + env_id: &str, + command: &[String], + _json: bool, +) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + engine.exec(&resolved, command).map_err(|e| e.to_string())?; + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/freeze.rs b/crates/karapace-cli/src/commands/freeze.rs new file mode 100644 index 0000000..e02171c --- /dev/null +++ b/crates/karapace-cli/src/commands/freeze.rs @@ -0,0 +1,14 @@ +use super::{resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, env_id: &str) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + engine.freeze(&resolved).map_err(|e| e.to_string())?; + println!("frozen environment {env_id}"); + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/gc.rs b/crates/karapace-cli/src/commands/gc.rs new file mode 100644 index 0000000..4835957 --- /dev/null +++ b/crates/karapace-cli/src/commands/gc.rs @@ -0,0 +1,33 @@ +use super::{json_pretty, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, dry_run: bool, json: bool) -> Result { + let layout = StoreLayout::new(store_path); + let lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let report = engine.gc(&lock, dry_run).map_err(|e| e.to_string())?; + if json { + let payload = serde_json::json!({ + "dry_run": dry_run, + "orphaned_envs": report.orphaned_envs, + "orphaned_layers": report.orphaned_layers, + "orphaned_objects": report.orphaned_objects, + "removed_envs": report.removed_envs, + "removed_layers": report.removed_layers, + "removed_objects": report.removed_objects, + }); + println!("{}", json_pretty(&payload)?); + } else { + let prefix = if dry_run { "would remove" } else { "removed" }; + println!( + "gc: {prefix} {} envs, {} layers, {} objects", + report.removed_envs, report.removed_layers, report.removed_objects + ); + if dry_run && !report.orphaned_envs.is_empty() { + println!("orphaned envs: {:?}", report.orphaned_envs); + } + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/inspect.rs b/crates/karapace-cli/src/commands/inspect.rs new file mode 100644 index 0000000..662f021 --- /dev/null +++ b/crates/karapace-cli/src/commands/inspect.rs @@ -0,0 +1,21 @@ +use super::{colorize_state, json_pretty, resolve_env_id, EXIT_SUCCESS}; +use karapace_core::Engine; + +pub fn run(engine: &Engine, env_id: &str, json: bool) -> Result { + let resolved = resolve_env_id(engine, env_id)?; + let meta = engine.inspect(&resolved).map_err(|e| e.to_string())?; + if json { + println!("{}", json_pretty(&meta)?); + } else { + println!("env_id: {}", meta.env_id); + println!("short_id: {}", meta.short_id); + println!("name: {}", meta.name.as_deref().unwrap_or("(none)")); + println!("state: {}", colorize_state(&meta.state.to_string())); + println!("base_layer: {}", meta.base_layer); + println!("deps: {}", meta.dependency_layers.len()); + println!("ref_count: {}", meta.ref_count); + println!("created_at: {}", meta.created_at); + println!("updated_at: {}", meta.updated_at); + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/list.rs b/crates/karapace-cli/src/commands/list.rs new file mode 100644 index 0000000..8e38460 --- /dev/null +++ b/crates/karapace-cli/src/commands/list.rs @@ -0,0 +1,22 @@ +use super::{colorize_state, json_pretty, EXIT_SUCCESS}; +use karapace_core::Engine; + +pub fn run(engine: &Engine, json: bool) -> Result { + let envs = engine.list().map_err(|e| e.to_string())?; + if json { + println!("{}", json_pretty(&envs)?); + } else if envs.is_empty() { + println!("no environments found"); + } else { + println!("{:<14} {:<16} {:<10} ENV_ID", "SHORT_ID", "NAME", "STATE"); + for env in &envs { + let name_display = env.name.as_deref().unwrap_or(""); + let state_str = colorize_state(&env.state.to_string()); + println!( + "{:<14} {:<16} {:<10} {}", + env.short_id, name_display, state_str, env.env_id + ); + } + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/man_pages.rs b/crates/karapace-cli/src/commands/man_pages.rs new file mode 100644 index 0000000..43e04d5 --- /dev/null +++ b/crates/karapace-cli/src/commands/man_pages.rs @@ -0,0 +1,26 @@ +use super::EXIT_SUCCESS; +use clap::CommandFactory; +use std::path::Path; + +pub fn run(dir: &Path) -> Result { + std::fs::create_dir_all(dir).map_err(|e| format!("failed to create dir: {e}"))?; + let cmd = C::command(); + let man = clap_mangen::Man::new(cmd.clone()); + let mut buf = Vec::new(); + man.render(&mut buf) + .map_err(|e| format!("man page render failed: {e}"))?; + let path = dir.join("karapace.1"); + std::fs::write(&path, &buf).map_err(|e| format!("failed to write {}: {e}", path.display()))?; + for sub in cmd.get_subcommands() { + let sub_name = format!("karapace-{}", sub.get_name()); + let man = clap_mangen::Man::new(sub.clone()); + let mut buf = Vec::new(); + man.render(&mut buf) + .map_err(|e| format!("man page render failed: {e}"))?; + let path = dir.join(format!("{sub_name}.1")); + std::fs::write(&path, &buf) + .map_err(|e| format!("failed to write {}: {e}", path.display()))?; + } + println!("man pages written to {}", dir.display()); + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/migrate.rs b/crates/karapace-cli/src/commands/migrate.rs new file mode 100644 index 0000000..ce5a30e --- /dev/null +++ b/crates/karapace-cli/src/commands/migrate.rs @@ -0,0 +1,100 @@ +use super::{EXIT_FAILURE, EXIT_SUCCESS}; +use std::path::Path; + +pub fn run(store_path: &Path, json_output: bool) -> Result { + let store_dir = store_path.join("store"); + if !store_dir.exists() { + msg( + json_output, + r#"{"status": "no_store", "message": "No store found."}"#, + &format!( + "No store found at {}. Nothing to migrate.", + store_path.display() + ), + ); + return Ok(EXIT_SUCCESS); + } + + let version_path = store_dir.join("version"); + if !version_path.exists() { + msg(json_output, + r#"{"status": "error", "message": "Store exists but has no version file."}"#, + &format!("Store at {} has no version file. May be corrupted or very old.\nRecommended: back up and rebuild.", store_path.display())); + return Ok(EXIT_FAILURE); + } + + let content = std::fs::read_to_string(&version_path) + .map_err(|e| format!("failed to read version file: {e}"))?; + let ver: serde_json::Value = + serde_json::from_str(&content).map_err(|e| format!("invalid version file: {e}"))?; + let found = ver + .get("format_version") + .and_then(serde_json::Value::as_u64) + .unwrap_or(0); + let current = u64::from(karapace_store::STORE_FORMAT_VERSION); + + if found == current { + msg( + json_output, + &format!(r#"{{"status": "current", "format_version": {current}}}"#), + &format!("Store format version: {current} (current)\nNo migration needed."), + ); + return Ok(EXIT_SUCCESS); + } + + if found > current { + msg(json_output, + &format!(r#"{{"status": "newer", "found": {found}, "supported": {current}}}"#), + &format!("Store format version: {found}\nSupported: {current}\n\nCreated by a newer Karapace. Please upgrade.")); + return Ok(EXIT_FAILURE); + } + + // Attempt automatic migration + match karapace_store::migrate_store(store_path) { + Ok(Some(result)) => { + msg( + json_output, + &format!( + r#"{{"status": "migrated", "from": {}, "to": {}, "environments": {}, "backup": "{}"}}"#, + result.from_version, + result.to_version, + result.environments_migrated, + result.backup_path.display() + ), + &format!( + "Migrated store from v{} to v{}.\n{} environments updated.\nBackup: {}", + result.from_version, + result.to_version, + result.environments_migrated, + result.backup_path.display() + ), + ); + Ok(EXIT_SUCCESS) + } + Ok(None) => { + // Should not reach here (we already checked found != current above) + msg( + json_output, + &format!(r#"{{"status": "current", "format_version": {current}}}"#), + &format!("Store format version: {current} (current)\nNo migration needed."), + ); + Ok(EXIT_SUCCESS) + } + Err(e) => { + msg( + json_output, + &format!(r#"{{"status": "error", "message": "{e}"}}"#), + &format!("Migration failed: {e}"), + ); + Ok(EXIT_FAILURE) + } + } +} + +fn msg(json_output: bool, json: &str, human: &str) { + if json_output { + println!("{json}"); + } else { + println!("{human}"); + } +} diff --git a/crates/karapace-cli/src/commands/mod.rs b/crates/karapace-cli/src/commands/mod.rs new file mode 100644 index 0000000..834af9d --- /dev/null +++ b/crates/karapace-cli/src/commands/mod.rs @@ -0,0 +1,209 @@ +pub mod archive; +pub mod build; +pub mod commit; +pub mod completions; +pub mod destroy; +pub mod diff; +pub mod doctor; +pub mod enter; +pub mod exec; +pub mod freeze; +pub mod gc; +pub mod inspect; +pub mod list; +pub mod man_pages; +pub mod migrate; +pub mod pull; +pub mod push; +pub mod rebuild; +pub mod rename; +pub mod restore; +pub mod snapshots; +pub mod stop; +pub mod verify_store; + +use indicatif::{ProgressBar, ProgressStyle}; +use karapace_core::Engine; +use std::time::Duration; + +pub const EXIT_SUCCESS: u8 = 0; +pub const EXIT_FAILURE: u8 = 1; +pub const EXIT_MANIFEST_ERROR: u8 = 2; +pub const EXIT_STORE_ERROR: u8 = 3; + +pub fn json_pretty(value: &impl serde::Serialize) -> Result { + serde_json::to_string_pretty(value).map_err(|e| format!("JSON serialization failed: {e}")) +} + +pub fn spinner(msg: &str) -> ProgressBar { + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::with_template("{spinner:.cyan} {msg}") + .expect("valid template") + .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), + ); + pb.set_message(msg.to_owned()); + pb.enable_steady_tick(Duration::from_millis(80)); + pb +} + +pub fn spin_ok(pb: &ProgressBar, msg: &str) { + pb.set_style(ProgressStyle::with_template("{msg}").expect("valid template")); + pb.finish_with_message(format!("✓ {msg}")); +} + +pub fn spin_fail(pb: &ProgressBar, msg: &str) { + pb.set_style(ProgressStyle::with_template("{msg}").expect("valid template")); + pb.finish_with_message(format!("✗ {msg}")); +} + +pub fn colorize_state(state: &str) -> String { + use console::Style; + match state { + "built" => Style::new().green().apply_to(state).to_string(), + "running" => Style::new().cyan().bold().apply_to(state).to_string(), + "defined" => Style::new().yellow().apply_to(state).to_string(), + "frozen" => Style::new().blue().apply_to(state).to_string(), + "archived" => Style::new().dim().apply_to(state).to_string(), + other => other.to_owned(), + } +} + +pub fn resolve_env_id(engine: &Engine, input: &str) -> Result { + if input.len() == 64 { + return Ok(input.to_owned()); + } + + let envs = engine.list().map_err(|e| e.to_string())?; + + for e in &envs { + if *e.env_id == *input || *e.short_id == *input || e.name.as_deref() == Some(input) { + return Ok(e.env_id.to_string()); + } + } + + let matches: Vec<_> = envs + .iter() + .filter(|e| e.env_id.starts_with(input) || e.short_id.starts_with(input)) + .collect(); + + match matches.len() { + 0 => Err(format!("no environment matching '{input}'")), + 1 => Ok(matches[0].env_id.to_string()), + n => Err(format!( + "ambiguous env_id prefix '{input}': matches {n} environments" + )), + } +} + +pub fn make_remote_backend( + remote_url: Option<&str>, +) -> Result { + let config = if let Some(url) = remote_url { + karapace_remote::RemoteConfig::new(url) + } else { + karapace_remote::RemoteConfig::load_default() + .map_err(|e| format!("no --remote and no config: {e}"))? + }; + Ok(karapace_remote::http::HttpBackend::new(config)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn json_pretty_serializes_string() { + let val = serde_json::json!({"key": "value"}); + let result = json_pretty(&val).unwrap(); + assert!(result.contains("\"key\"")); + assert!(result.contains("\"value\"")); + } + + #[test] + fn json_pretty_serializes_array() { + let val = vec![1, 2, 3]; + let result = json_pretty(&val).unwrap(); + assert!(result.contains('1')); + } + + #[test] + fn colorize_state_built() { + let result = colorize_state("built"); + assert!(result.contains("built")); + } + + #[test] + fn colorize_state_running() { + let result = colorize_state("running"); + assert!(result.contains("running")); + } + + #[test] + fn colorize_state_defined() { + let result = colorize_state("defined"); + assert!(result.contains("defined")); + } + + #[test] + fn colorize_state_frozen() { + let result = colorize_state("frozen"); + assert!(result.contains("frozen")); + } + + #[test] + fn colorize_state_archived() { + let result = colorize_state("archived"); + assert!(result.contains("archived")); + } + + #[test] + fn colorize_state_unknown() { + assert_eq!(colorize_state("unknown"), "unknown"); + } + + #[test] + fn resolve_env_id_64_char_passthrough() { + let dir = tempfile::tempdir().unwrap(); + let engine = Engine::new(dir.path()); + let id = "a".repeat(64); + assert_eq!(resolve_env_id(&engine, &id).unwrap(), id); + } + + #[test] + fn resolve_env_id_not_found() { + let dir = tempfile::tempdir().unwrap(); + let engine = Engine::new(dir.path()); + karapace_store::StoreLayout::new(dir.path()) + .initialize() + .unwrap(); + let result = resolve_env_id(&engine, "nonexistent"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("no environment matching")); + } + + #[test] + fn exit_codes_are_distinct() { + assert_ne!(EXIT_SUCCESS, EXIT_FAILURE); + assert_ne!(EXIT_FAILURE, EXIT_MANIFEST_ERROR); + assert_ne!(EXIT_MANIFEST_ERROR, EXIT_STORE_ERROR); + } + + #[test] + fn make_remote_backend_with_url() { + let backend = make_remote_backend(Some("http://localhost:8080")); + assert!(backend.is_ok()); + } + + #[test] + fn spinner_creates_progress_bar() { + let pb = spinner("testing..."); + spin_ok(&pb, "done"); + } + + #[test] + fn spinner_fail_creates_progress_bar() { + let pb = spinner("testing..."); + spin_fail(&pb, "failed"); + } +} diff --git a/crates/karapace-cli/src/commands/pull.rs b/crates/karapace-cli/src/commands/pull.rs new file mode 100644 index 0000000..6431c36 --- /dev/null +++ b/crates/karapace-cli/src/commands/pull.rs @@ -0,0 +1,44 @@ +use super::{json_pretty, make_remote_backend, spin_fail, spin_ok, spinner, EXIT_SUCCESS}; +use karapace_core::Engine; + +pub fn run( + engine: &Engine, + reference: &str, + remote_url: Option<&str>, + json: bool, +) -> Result { + let backend = make_remote_backend(remote_url)?; + + // Resolve reference: try as registry ref first, fall back to raw env_id + let env_id = match Engine::resolve_remote_ref(&backend, reference) { + Ok(id) => id, + Err(_) => reference.to_owned(), + }; + + let pb = spinner("pulling environment…"); + let result = engine.pull(&env_id, &backend).map_err(|e| { + spin_fail(&pb, "pull failed"); + e.to_string() + })?; + spin_ok(&pb, "pull complete"); + + if json { + let payload = serde_json::json!({ + "env_id": env_id, + "objects_pulled": result.objects_pulled, + "layers_pulled": result.layers_pulled, + "objects_skipped": result.objects_skipped, + "layers_skipped": result.layers_skipped, + }); + println!("{}", json_pretty(&payload)?); + } else { + println!( + "pulled {} ({} objects, {} layers; {} skipped)", + &env_id[..12.min(env_id.len())], + result.objects_pulled, + result.layers_pulled, + result.objects_skipped + result.layers_skipped, + ); + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/push.rs b/crates/karapace-cli/src/commands/push.rs new file mode 100644 index 0000000..b947243 --- /dev/null +++ b/crates/karapace-cli/src/commands/push.rs @@ -0,0 +1,46 @@ +use super::{ + json_pretty, make_remote_backend, resolve_env_id, spin_fail, spin_ok, spinner, EXIT_SUCCESS, +}; +use karapace_core::Engine; + +pub fn run( + engine: &Engine, + env_id: &str, + tag: Option<&str>, + remote_url: Option<&str>, + json: bool, +) -> Result { + let resolved = resolve_env_id(engine, env_id)?; + let backend = make_remote_backend(remote_url)?; + + let pb = spinner("pushing environment…"); + let result = engine.push(&resolved, &backend, tag).map_err(|e| { + spin_fail(&pb, "push failed"); + e.to_string() + })?; + spin_ok(&pb, "push complete"); + + if json { + let payload = serde_json::json!({ + "env_id": resolved, + "tag": tag, + "objects_pushed": result.objects_pushed, + "layers_pushed": result.layers_pushed, + "objects_skipped": result.objects_skipped, + "layers_skipped": result.layers_skipped, + }); + println!("{}", json_pretty(&payload)?); + } else { + println!( + "pushed {} ({} objects, {} layers; {} skipped)", + &resolved[..12], + result.objects_pushed, + result.layers_pushed, + result.objects_skipped + result.layers_skipped, + ); + if let Some(t) = tag { + println!("tagged as '{t}'"); + } + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/rebuild.rs b/crates/karapace-cli/src/commands/rebuild.rs new file mode 100644 index 0000000..0f2ee92 --- /dev/null +++ b/crates/karapace-cli/src/commands/rebuild.rs @@ -0,0 +1,57 @@ +use super::{json_pretty, spin_fail, spin_ok, spinner, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run( + engine: &Engine, + store_path: &Path, + manifest: &Path, + name: Option<&str>, + json: bool, +) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let pb = if json { + None + } else { + Some(spinner("rebuilding environment...")) + }; + let result = match engine.rebuild(manifest) { + Ok(r) => { + if let Some(ref pb) = pb { + spin_ok(pb, "environment rebuilt"); + } + r + } + Err(e) => { + if let Some(ref pb) = pb { + spin_fail(pb, "rebuild failed"); + } + return Err(e.to_string()); + } + }; + if let Some(n) = name { + engine + .set_name(&result.identity.env_id, Some(n.to_owned())) + .map_err(|e| e.to_string())?; + } + if json { + let payload = serde_json::json!({ + "env_id": result.identity.env_id, + "short_id": result.identity.short_id, + "name": name, + "status": "rebuilt" + }); + println!("{}", json_pretty(&payload)?); + } else { + if let Some(n) = name { + println!("rebuilt environment '{}' ({})", n, result.identity.short_id); + } else { + println!("rebuilt environment {}", result.identity.short_id); + } + println!("env_id: {}", result.identity.env_id); + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/rename.rs b/crates/karapace-cli/src/commands/rename.rs new file mode 100644 index 0000000..55c43ff --- /dev/null +++ b/crates/karapace-cli/src/commands/rename.rs @@ -0,0 +1,16 @@ +use super::{resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, env_id: &str, new_name: &str) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + engine + .rename(&resolved, new_name) + .map_err(|e| e.to_string())?; + println!("renamed {} → '{}'", &resolved[..12], new_name); + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/restore.rs b/crates/karapace-cli/src/commands/restore.rs new file mode 100644 index 0000000..b2018c8 --- /dev/null +++ b/crates/karapace-cli/src/commands/restore.rs @@ -0,0 +1,31 @@ +use super::{json_pretty, resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run( + engine: &Engine, + store_path: &Path, + env_id: &str, + snapshot_hash: &str, + json: bool, +) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + engine + .restore(&resolved, snapshot_hash) + .map_err(|e| e.to_string())?; + + if json { + let payload = serde_json::json!({ + "env_id": resolved, + "restored_snapshot": snapshot_hash, + }); + println!("{}", json_pretty(&payload)?); + } else { + println!("restored {env_id} from snapshot {snapshot_hash}"); + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/snapshots.rs b/crates/karapace-cli/src/commands/snapshots.rs new file mode 100644 index 0000000..1c0aeb3 --- /dev/null +++ b/crates/karapace-cli/src/commands/snapshots.rs @@ -0,0 +1,39 @@ +use super::{json_pretty, resolve_env_id, EXIT_SUCCESS}; +use karapace_core::Engine; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, env_id: &str, json: bool) -> Result { + let _layout = StoreLayout::new(store_path); + + let resolved = resolve_env_id(engine, env_id)?; + let snapshots = engine + .list_snapshots(&resolved) + .map_err(|e| e.to_string())?; + + if json { + let entries: Vec<_> = snapshots + .iter() + .map(|s| { + serde_json::json!({ + "hash": s.hash, + "tar_hash": s.tar_hash, + "parent": s.parent, + }) + }) + .collect(); + let payload = serde_json::json!({ + "env_id": resolved, + "snapshots": entries, + }); + println!("{}", json_pretty(&payload)?); + } else if snapshots.is_empty() { + println!("no snapshots for {env_id}"); + } else { + println!("snapshots for {env_id}:"); + for s in &snapshots { + println!(" {} (tar: {})", &s.hash[..12], &s.tar_hash[..12]); + } + } + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/stop.rs b/crates/karapace-cli/src/commands/stop.rs new file mode 100644 index 0000000..0ad8ea5 --- /dev/null +++ b/crates/karapace-cli/src/commands/stop.rs @@ -0,0 +1,14 @@ +use super::{resolve_env_id, EXIT_SUCCESS}; +use karapace_core::{Engine, StoreLock}; +use karapace_store::StoreLayout; +use std::path::Path; + +pub fn run(engine: &Engine, store_path: &Path, env_id: &str) -> Result { + let layout = StoreLayout::new(store_path); + let _lock = StoreLock::acquire(&layout.lock_file()).map_err(|e| format!("store lock: {e}"))?; + + let resolved = resolve_env_id(engine, env_id)?; + engine.stop(&resolved).map_err(|e| e.to_string())?; + println!("stopped environment {env_id}"); + Ok(EXIT_SUCCESS) +} diff --git a/crates/karapace-cli/src/commands/verify_store.rs b/crates/karapace-cli/src/commands/verify_store.rs new file mode 100644 index 0000000..742ed2f --- /dev/null +++ b/crates/karapace-cli/src/commands/verify_store.rs @@ -0,0 +1,30 @@ +use super::{json_pretty, EXIT_STORE_ERROR, EXIT_SUCCESS}; +use karapace_core::Engine; +use karapace_store::verify_store_integrity; + +pub fn run(engine: &Engine, json: bool) -> Result { + let report = verify_store_integrity(engine.store_layout()).map_err(|e| e.to_string())?; + + if json { + let payload = serde_json::json!({ + "checked": report.checked, + "passed": report.passed, + "failed": report.failed.len(), + }); + println!("{}", json_pretty(&payload)?); + } else { + println!( + "store integrity: {}/{} objects passed", + report.passed, report.checked + ); + for f in &report.failed { + println!(" FAIL {}: {}", f.hash, f.reason); + } + } + + if report.failed.is_empty() { + Ok(EXIT_SUCCESS) + } else { + Ok(EXIT_STORE_ERROR) + } +} diff --git a/crates/karapace-cli/src/main.rs b/crates/karapace-cli/src/main.rs new file mode 100644 index 0000000..16f7211 --- /dev/null +++ b/crates/karapace-cli/src/main.rs @@ -0,0 +1,302 @@ +mod commands; + +use clap::{Parser, Subcommand}; +use clap_complete::Shell; +use commands::{EXIT_FAILURE, EXIT_MANIFEST_ERROR, EXIT_STORE_ERROR}; +use karapace_core::{install_signal_handler, Engine}; +use std::path::PathBuf; +use std::process::ExitCode; + +#[derive(Debug, Parser)] +#[command( + name = "karapace", + version, + about = "Deterministic environment engine for immutable systems" +)] +struct Cli { + /// Path to the Karapace store directory. + #[arg(long, default_value = "~/.local/share/karapace")] + store: String, + + /// Output results as structured JSON. + #[arg(long, default_value_t = false, global = true)] + json: bool, + + /// Enable verbose (debug) logging output. + #[arg(short, long, default_value_t = false, global = true)] + verbose: bool, + + /// Enable trace-level logging (more detailed than --verbose). + #[arg(long, default_value_t = false, global = true)] + trace: bool, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Build an environment from a manifest. + Build { + /// Path to manifest TOML file. + #[arg(default_value = "karapace.toml")] + manifest: PathBuf, + /// Human-readable name for the environment. + #[arg(long)] + name: Option, + }, + /// Destroy and rebuild an environment from manifest. + Rebuild { + /// Path to manifest TOML file. + #[arg(default_value = "karapace.toml")] + manifest: PathBuf, + /// Human-readable name for the environment. + #[arg(long)] + name: Option, + }, + /// Enter a built environment (use -- to pass a command instead of interactive shell). + Enter { + /// Environment ID (full or short). + env_id: String, + /// Command to run inside the environment (after --). + #[arg(last = true)] + command: Vec, + }, + /// Execute a command inside a built environment (non-interactive). + Exec { + /// Environment ID (full or short). + env_id: String, + /// Command and arguments to run. + #[arg(required = true, last = true)] + command: Vec, + }, + /// Destroy an environment and its overlay. + Destroy { + /// Environment ID. + env_id: String, + }, + /// Stop a running environment. + Stop { + /// Environment ID. + env_id: String, + }, + /// Freeze an environment (prevent further writes). + Freeze { + /// Environment ID. + env_id: String, + }, + /// Archive an environment (preserve but prevent entry). + Archive { + /// Environment ID. + env_id: String, + }, + /// List all known environments. + List, + /// Inspect environment metadata. + Inspect { + /// Environment ID. + env_id: String, + }, + /// Show drift in the writable overlay of an environment. + Diff { + /// Environment ID. + env_id: String, + }, + /// List snapshots for an environment. + Snapshots { + /// Environment ID. + env_id: String, + }, + /// Commit overlay drift into the content store as a snapshot. + Commit { + /// Environment ID. + env_id: String, + }, + /// Restore an environment's overlay from a snapshot. + Restore { + /// Environment ID. + env_id: String, + /// Snapshot layer hash to restore from. + snapshot: String, + }, + /// Run garbage collection on the store. + Gc { + /// Only report what would be removed. + #[arg(long, default_value_t = false)] + dry_run: bool, + }, + /// Verify store integrity. + VerifyStore, + /// Push an environment to a remote store. + Push { + /// Environment ID, short ID, or name. + env_id: String, + /// Registry tag (e.g. "my-env@latest"). If omitted, pushed without a tag. + #[arg(long)] + tag: Option, + /// Remote store URL (overrides config file). + #[arg(long)] + remote: Option, + }, + /// Pull an environment from a remote store. + Pull { + /// Registry reference (e.g. "my-env@latest") or raw env_id. + reference: String, + /// Remote store URL (overrides config file). + #[arg(long)] + remote: Option, + }, + /// Rename an environment. + Rename { + /// Environment ID or current name. + env_id: String, + /// New name for the environment. + new_name: String, + }, + /// Generate shell completions for bash, zsh, fish, elvish, or powershell. + Completions { + /// Shell to generate completions for. + shell: Shell, + }, + /// Generate man pages in the specified directory. + ManPages { + /// Output directory for man pages. + #[arg(default_value = "man")] + dir: PathBuf, + }, + /// Run diagnostic checks on the system and store. + Doctor, + /// Check store version and show migration guidance. + Migrate, +} + +#[allow(clippy::too_many_lines)] +fn main() -> ExitCode { + let cli = Cli::parse(); + + let default_level = if cli.trace { + "trace" + } else if cli.verbose { + "debug" + } else { + "warn" + }; + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_env("KARAPACE_LOG") + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(default_level)), + ) + .with_target(false) + .without_time() + .init(); + + install_signal_handler(); + + let store_path = expand_tilde(&cli.store); + let engine = Engine::new(&store_path); + let json_output = cli.json; + + let needs_runtime = matches!( + cli.command, + Commands::Build { .. } + | Commands::Enter { .. } + | Commands::Exec { .. } + | Commands::Rebuild { .. } + ); + if needs_runtime { + let missing = karapace_runtime::check_namespace_prereqs(); + if !missing.is_empty() { + eprintln!("error: {}", karapace_runtime::format_missing(&missing)); + return ExitCode::from(EXIT_FAILURE); + } + } + + let result = match cli.command { + Commands::Build { manifest, name } => commands::build::run( + &engine, + &store_path, + &manifest, + name.as_deref(), + json_output, + ), + Commands::Rebuild { manifest, name } => commands::rebuild::run( + &engine, + &store_path, + &manifest, + name.as_deref(), + json_output, + ), + Commands::Enter { env_id, command } => { + commands::enter::run(&engine, &store_path, &env_id, &command) + } + Commands::Exec { env_id, command } => { + commands::exec::run(&engine, &store_path, &env_id, &command, json_output) + } + Commands::Destroy { env_id } => commands::destroy::run(&engine, &store_path, &env_id), + Commands::Stop { env_id } => commands::stop::run(&engine, &store_path, &env_id), + Commands::Freeze { env_id } => commands::freeze::run(&engine, &store_path, &env_id), + Commands::Archive { env_id } => commands::archive::run(&engine, &store_path, &env_id), + Commands::List => commands::list::run(&engine, json_output), + Commands::Inspect { env_id } => commands::inspect::run(&engine, &env_id, json_output), + Commands::Diff { env_id } => commands::diff::run(&engine, &env_id, json_output), + Commands::Snapshots { env_id } => { + commands::snapshots::run(&engine, &store_path, &env_id, json_output) + } + Commands::Commit { env_id } => { + commands::commit::run(&engine, &store_path, &env_id, json_output) + } + Commands::Restore { env_id, snapshot } => { + commands::restore::run(&engine, &store_path, &env_id, &snapshot, json_output) + } + Commands::Gc { dry_run } => commands::gc::run(&engine, &store_path, dry_run, json_output), + Commands::VerifyStore => commands::verify_store::run(&engine, json_output), + Commands::Push { + env_id, + tag, + remote, + } => commands::push::run( + &engine, + &env_id, + tag.as_deref(), + remote.as_deref(), + json_output, + ), + Commands::Pull { reference, remote } => { + commands::pull::run(&engine, &reference, remote.as_deref(), json_output) + } + Commands::Rename { env_id, new_name } => { + commands::rename::run(&engine, &store_path, &env_id, &new_name) + } + Commands::Completions { shell } => commands::completions::run::(shell), + Commands::ManPages { dir } => commands::man_pages::run::(&dir), + Commands::Doctor => commands::doctor::run(&store_path, json_output), + Commands::Migrate => commands::migrate::run(&store_path, json_output), + }; + + match result { + Ok(code) => ExitCode::from(code), + Err(msg) => { + eprintln!("error: {msg}"); + let code = if msg.starts_with("manifest error:") + || msg.starts_with("failed to parse manifest") + || msg.starts_with("failed to read manifest") + { + EXIT_MANIFEST_ERROR + } else if msg.starts_with("store error:") || msg.starts_with("store lock:") { + EXIT_STORE_ERROR + } else { + EXIT_FAILURE + }; + ExitCode::from(code) + } + } +} + +fn expand_tilde(path: &str) -> PathBuf { + if let Some(stripped) = path.strip_prefix("~/") { + if let Ok(home) = std::env::var("HOME") { + return PathBuf::from(home).join(stripped); + } + } + PathBuf::from(path) +} diff --git a/crates/karapace-cli/tests/cli_integration.rs b/crates/karapace-cli/tests/cli_integration.rs new file mode 100644 index 0000000..4e1c83a --- /dev/null +++ b/crates/karapace-cli/tests/cli_integration.rs @@ -0,0 +1,310 @@ +//! CLI subprocess integration tests. +//! +//! These tests invoke the `karapace` binary as a subprocess and verify +//! exit codes, stdout content, and JSON output stability. + +use std::process::Command; + +fn karapace_bin() -> Command { + Command::new(env!("CARGO_BIN_EXE_karapace")) +} + +fn temp_store() -> tempfile::TempDir { + tempfile::tempdir().unwrap() +} + +fn write_test_manifest(dir: &std::path::Path) -> std::path::PathBuf { + let path = dir.join("karapace.toml"); + std::fs::write( + &path, + r#"manifest_version = 1 + +[base] +image = "rolling" + +[system] +packages = ["git"] + +[runtime] +backend = "mock" +"#, + ) + .unwrap(); + path +} + +// A5: CLI Validation — version flag +#[test] +fn cli_version_exits_zero() { + let output = karapace_bin().arg("--version").output().unwrap(); + assert!(output.status.success(), "karapace --version must exit 0"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("karapace"), + "version output must contain 'karapace': {stdout}" + ); +} + +// A5: CLI Validation — help flag +#[test] +fn cli_help_exits_zero() { + let output = karapace_bin().arg("--help").output().unwrap(); + assert!(output.status.success(), "karapace --help must exit 0"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("build"), "help must list 'build' command"); + assert!( + stdout.contains("destroy"), + "help must list 'destroy' command" + ); +} + +// A5: CLI Validation — build with mock backend +#[test] +fn cli_build_succeeds_with_mock() { + let store = temp_store(); + let project = tempfile::tempdir().unwrap(); + let manifest = write_test_manifest(project.path()); + + let output = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "build", + &manifest.to_string_lossy(), + ]) + .output() + .unwrap(); + + assert!( + output.status.success(), + "build must exit 0. stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); +} + +// A5: CLI Validation — list with JSON output +#[test] +fn cli_list_json_output_stable() { + let store = temp_store(); + let project = tempfile::tempdir().unwrap(); + let manifest = write_test_manifest(project.path()); + + // Build first + let build_out = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "build", + &manifest.to_string_lossy(), + ]) + .output() + .unwrap(); + assert!(build_out.status.success()); + + // List with --json + let output = karapace_bin() + .args(["--store", &store.path().to_string_lossy(), "--json", "list"]) + .output() + .unwrap(); + + assert!( + output.status.success(), + "list --json must exit 0. stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let stdout = String::from_utf8_lossy(&output.stdout); + // Must be valid JSON + let parsed: serde_json::Value = serde_json::from_str(&stdout) + .unwrap_or_else(|e| panic!("list --json must produce valid JSON: {e}\nstdout: {stdout}")); + assert!(parsed.is_array(), "list output must be a JSON array"); + let arr = parsed.as_array().unwrap(); + assert_eq!(arr.len(), 1, "should have exactly 1 environment"); + // Verify expected fields exist + assert!(arr[0]["env_id"].is_string()); + assert!(arr[0]["state"].is_string()); +} + +// A5: CLI Validation — inspect with JSON output +#[test] +fn cli_inspect_json_output_stable() { + let store = temp_store(); + let project = tempfile::tempdir().unwrap(); + let manifest = write_test_manifest(project.path()); + + // Build first + let build_out = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "--json", + "build", + &manifest.to_string_lossy(), + ]) + .output() + .unwrap(); + assert!(build_out.status.success()); + + // Parse build JSON to get env_id + let build_stdout = String::from_utf8_lossy(&build_out.stdout); + let build_json: serde_json::Value = serde_json::from_str(&build_stdout) + .unwrap_or_else(|e| panic!("build --json must produce valid JSON: {e}\n{build_stdout}")); + let env_id = build_json["env_id"].as_str().unwrap(); + + // Inspect + let output = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "--json", + "inspect", + env_id, + ]) + .output() + .unwrap(); + + assert!(output.status.success()); + let stdout = String::from_utf8_lossy(&output.stdout); + let inspect_json: serde_json::Value = serde_json::from_str(&stdout) + .unwrap_or_else(|e| panic!("inspect --json must produce valid JSON: {e}\n{stdout}")); + assert_eq!(inspect_json["env_id"].as_str().unwrap(), env_id); + assert_eq!(inspect_json["state"].as_str().unwrap(), "Built"); +} + +// A5: CLI Validation — destroy succeeds +#[test] +fn cli_destroy_succeeds() { + let store = temp_store(); + let project = tempfile::tempdir().unwrap(); + let manifest = write_test_manifest(project.path()); + + // Build + let build_out = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "--json", + "build", + &manifest.to_string_lossy(), + ]) + .output() + .unwrap(); + assert!(build_out.status.success()); + let build_json: serde_json::Value = + serde_json::from_str(&String::from_utf8_lossy(&build_out.stdout)).unwrap(); + let env_id = build_json["env_id"].as_str().unwrap(); + + // Destroy + let output = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "destroy", + env_id, + ]) + .output() + .unwrap(); + + assert!( + output.status.success(), + "destroy must exit 0. stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); +} + +// A5: CLI Validation — build with nonexistent manifest fails +#[test] +fn cli_build_nonexistent_manifest_fails() { + let store = temp_store(); + + let output = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "build", + "/tmp/nonexistent_karapace_manifest_12345.toml", + ]) + .output() + .unwrap(); + + assert!( + !output.status.success(), + "build with missing manifest must fail" + ); +} + +// A5: CLI Validation — gc on empty store succeeds +#[test] +fn cli_gc_empty_store_succeeds() { + let store = temp_store(); + + // Initialize store first via a build + let project = tempfile::tempdir().unwrap(); + let manifest = write_test_manifest(project.path()); + let _ = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "build", + &manifest.to_string_lossy(), + ]) + .output() + .unwrap(); + + let output = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "--json", + "gc", + "--dry-run", + ]) + .output() + .unwrap(); + + assert!( + output.status.success(), + "gc --dry-run must exit 0. stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); +} + +// A5: CLI Validation — verify-store on clean store +#[test] +fn cli_verify_store_clean() { + let store = temp_store(); + let project = tempfile::tempdir().unwrap(); + let manifest = write_test_manifest(project.path()); + + // Build to populate store + let _ = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "build", + &manifest.to_string_lossy(), + ]) + .output() + .unwrap(); + + let output = karapace_bin() + .args([ + "--store", + &store.path().to_string_lossy(), + "--json", + "verify-store", + ]) + .output() + .unwrap(); + + assert!( + output.status.success(), + "verify-store must exit 0. stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let stdout = String::from_utf8_lossy(&output.stdout); + let json: serde_json::Value = serde_json::from_str(&stdout) + .unwrap_or_else(|e| panic!("verify-store --json must produce valid JSON: {e}\n{stdout}")); + assert_eq!(json["failed"].as_u64().unwrap(), 0); +}