Skip to content

Commit 7fc9447

Browse files
committed
refactor: replace string registry with enum and update related logic
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
1 parent 58f9e0c commit 7fc9447

7 files changed

Lines changed: 249 additions & 127 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name: CI
33
on:
44
pull_request:
55
branches: [ "main" ]
6+
paths:
7+
- '**/*.rs'
8+
- '**/*.toml'
9+
- 'Cargo.lock'
610

711
env:
812
CARGO_TERM_COLOR: always

AGENTS.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
## General
22

3-
Always prefer `cargo clippy` over cargo check
4-
Always format the code with `cargo fmt` and run the test suite with `cargo test` before finalizing
5-
Always prefer `cargo add` over manually editing Cargo.toml
3+
Always prefer `cargo clippy` over cargo check.
64

5+
Always format the code with `cargo fmt` and run the test suite with `cargo test` before finalizing if any rust code was
6+
touched.
7+
8+
Always prefer `cargo add` over manually editing `Cargo.toml`.
9+
10+
Always prefer enums over strings when there's a clear set of valid values.

README.md

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,104 @@
1-
# cf-cli
2-
CyberFabric repositories automation tool
1+
# CyberFabric CLI
2+
3+
Command-line interface for development and management of CyberFabric modules.
4+
5+
## Quickstart
6+
7+
### Prerequisites
8+
9+
- Rust toolchain with `cargo`
10+
- A local clone of this repository
11+
12+
### Install the CLI
13+
14+
This workspace exposes two binaries:
15+
16+
- `cyberfabric`
17+
- `cargo-cyberfabric` for the `cargo cyberfabric ...` invocation form
18+
19+
Install both from the repository root:
20+
21+
```bash
22+
cargo install --path crates/cli --bin cyberfabric --bin cargo-cyberfabric
23+
```
24+
25+
After installation, you can use either form:
26+
27+
```bash
28+
cyberfabric --help
29+
```
30+
31+
```bash
32+
cargo cyberfabric --help
33+
```
34+
35+
For local development without installing:
36+
37+
```bash
38+
cargo run -p cli -- --help
39+
```
40+
41+
## What the CLI can do
42+
43+
The current CLI surface is centered on CyberFabric workspace setup, configuration, code generation, and execution.
44+
45+
### Workspace scaffolding
46+
47+
- `mod init` initializes a new CyberFabric workspace from a template
48+
- `mod add` adds module templates such as `background-worker`, `api-db-handler`, and `rest-gateway`
49+
50+
### Configuration management
51+
52+
- `config mod list` inspects available and configured modules
53+
- `config mod add` and `config mod rm` manage module entries in the YAML config
54+
- `config mod db add|edit|rm` manages module-level database settings
55+
- `config db add|edit|rm` manages shared database server definitions
56+
57+
You need to provide the path to the configuration file with the `-c` flag. `-c config/quickstart.yml`
58+
59+
### Build and run generated servers
60+
61+
- `build` generates a runnable Cargo project under `.cyberfabric/` and builds it based on the `-c` configuration
62+
provided.
63+
- `run` generates the same project and runs it. You can provide `-w` to enable watch mode and/or `--otel` to enable
64+
OpenTelemetry.
65+
66+
### Source inspection
67+
68+
- `docs` resolves Rust source for crates, modules, and items from the workspace, local cache, or `crates.io`
69+
70+
### Tool bootstrap
71+
72+
- `tools` installs or upgrades `rustup`, `rustfmt`, and `clippy`
73+
74+
### Current placeholders
75+
76+
- `lint` is declared but not implemented yet
77+
- `test` is declared but not implemented yet
78+
79+
## Typical usage flow
80+
81+
Create a workspace, add a module, configure it, and run it:
82+
83+
```bash
84+
cyberfabric mod init /tmp/cf-demo
85+
cyberfabric mod add background-worker -p /tmp/cf-demo
86+
cyberfabric config mod add background-worker -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml
87+
cyberfabric run -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml
88+
```
89+
90+
The `-p` is to specify the path. If you don't provide it, the default will be the current directory.
91+
92+
## Command overview
93+
94+
For the full command surface, arguments, and examples, check [SKILLS.md](SKILLS.md).
95+
96+
## License
97+
98+
This project is licensed under the Apache License, Version 2.0.
99+
100+
- Full license text: `LICENSE`
101+
- License URL: <http://www.apache.org/licenses/LICENSE-2.0>
102+
103+
Unless required by applicable law or agreed to in writing, the software is distributed on an `AS IS` basis, without
104+
warranties or conditions of any kind.

crates/cli/src/common.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use anyhow::Context;
2-
use clap::Args;
2+
use clap::{Args, ValueEnum};
33
use module_parser::{
44
CargoToml, CargoTomlDependencies, CargoTomlDependency, Config, ConfigModuleMetadata,
55
get_dependencies, get_module_name_from_crate,
66
};
77
use std::collections::HashMap;
8+
use std::fmt::{self, Display};
89
use std::fs;
910
use std::path::{Path, PathBuf};
1011
use std::process::Command;
@@ -49,6 +50,27 @@ pub struct BuildRunArgs {
4950
pub clean: bool,
5051
}
5152

53+
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
54+
pub enum Registry {
55+
#[default]
56+
#[value(name = "crates.io")]
57+
CratesIo,
58+
}
59+
60+
impl Registry {
61+
pub const fn as_str(self) -> &'static str {
62+
match self {
63+
Self::CratesIo => "crates.io",
64+
}
65+
}
66+
}
67+
68+
impl Display for Registry {
69+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70+
f.write_str(self.as_str())
71+
}
72+
}
73+
5274
impl BuildRunArgs {
5375
pub fn resolve_workspace_and_config(&self) -> anyhow::Result<(PathBuf, PathBuf)> {
5476
let path = self.path_config.resolve_path()?;

crates/cli/src/config/modules/list.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{SYSTEM_REGISTRY_MODULES, SystemRegistryModule, load_config, resolve_modules_context};
2-
use crate::common::PathConfigArgs;
2+
use crate::common::{PathConfigArgs, Registry};
33
use crate::config::app_config::ModuleConfig;
44
use anyhow::{Context, bail};
55
use clap::Args;
@@ -27,9 +27,11 @@ pub struct ListArgs {
2727
/// Show all information related to the module.
2828
#[arg(short = 'v', long)]
2929
verbose: bool,
30-
/// Registry to query when verbose mode is enabled.
31-
#[arg(long, default_value = "crates.io")]
32-
registry: String,
30+
/// Registry to query for system-crate metadata. Only consulted when both
31+
/// `--system` and `--verbose` are enabled; `--verbose` alone does not query
32+
/// any registry. Defaults to `crates.io`.
33+
#[arg(long, value_enum, default_value_t = Registry::CratesIo)]
34+
registry: Registry,
3335
}
3436

3537
impl ListArgs {
@@ -42,19 +44,13 @@ impl ListArgs {
4244
if self.system {
4345
println!("System crates:");
4446
if self.verbose {
45-
if self.registry != "crates.io" {
46-
let registry = &self.registry;
47-
bail!(
48-
"unsupported registry '{registry}'. Only 'crates.io' is currently supported"
49-
);
50-
}
51-
5247
let runtime = tokio::runtime::Builder::new_current_thread()
5348
.enable_all()
5449
.build()
5550
.context("failed to build tokio runtime for registry queries")?;
5651

57-
let metadata_by_crate = runtime.block_on(fetch_all_crates_io_metadata())?;
52+
let metadata_by_crate =
53+
runtime.block_on(fetch_all_registry_metadata(self.registry))?;
5854

5955
for module in SYSTEM_REGISTRY_MODULES {
6056
let Some(metadata) = metadata_by_crate.get(module.crate_name) else {
@@ -208,7 +204,9 @@ struct CrateVersion {
208204
features: BTreeMap<String, Vec<String>>,
209205
}
210206

211-
async fn fetch_all_crates_io_metadata() -> anyhow::Result<HashMap<&'static str, RegistryMetadata>> {
207+
async fn fetch_all_registry_metadata(
208+
registry: Registry,
209+
) -> anyhow::Result<HashMap<&'static str, RegistryMetadata>> {
212210
let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(4));
213211
let client = Client::builder()
214212
.user_agent("cyberfabric-cli")
@@ -225,7 +223,7 @@ async fn fetch_all_crates_io_metadata() -> anyhow::Result<HashMap<&'static str,
225223
.acquire_owned()
226224
.await
227225
.context("failed to acquire registry fetch permit")?;
228-
let metadata = fetch_crates_io_metadata(&cloned_client, module)
226+
let metadata = fetch_registry_metadata(&cloned_client, registry, module)
229227
.await
230228
.with_context(|| format!("failed to fetch metadata for '{}'", module.crate_name))?;
231229
Ok::<_, anyhow::Error>((module.crate_name, metadata))
@@ -241,11 +239,12 @@ async fn fetch_all_crates_io_metadata() -> anyhow::Result<HashMap<&'static str,
241239
Ok(metadata_by_crate)
242240
}
243241

244-
async fn fetch_crates_io_metadata(
242+
async fn fetch_registry_metadata(
245243
client: &Client,
244+
registry: Registry,
246245
module: SystemRegistryModule,
247246
) -> anyhow::Result<RegistryMetadata> {
248-
let crate_url = format!("https://crates.io/api/v1/crates/{}", module.crate_name);
247+
let crate_url = format!("https://{registry}/api/v1/crates/{}", module.crate_name);
249248
let crate_response = client
250249
.get(&crate_url)
251250
.send()
@@ -264,7 +263,8 @@ async fn fetch_crates_io_metadata(
264263
.find(|version| version.num == latest_version)
265264
.map_or_else(Vec::new, |version| version.features.into_keys().collect());
266265

267-
let module_rs_content = fetch_module_rs_content(client, module, &latest_version).await?;
266+
let module_rs_content =
267+
fetch_module_rs_content(client, registry, module, &latest_version).await?;
268268
let module_metadata = parse_module_rs_source(&module_rs_content)
269269
.with_context(|| format!("invalid src/module.rs for {}", module.crate_name))?;
270270

@@ -278,11 +278,12 @@ async fn fetch_crates_io_metadata(
278278

279279
async fn fetch_module_rs_content(
280280
client: &Client,
281+
registry: Registry,
281282
module: SystemRegistryModule,
282283
latest_version: &str,
283284
) -> anyhow::Result<String> {
284285
let download_url = format!(
285-
"https://crates.io/api/v1/crates/{}/{}/download",
286+
"https://{registry}/api/v1/crates/{}/{}/download",
286287
module.crate_name, latest_version
287288
);
288289
let crate_archive = client

0 commit comments

Comments
 (0)