Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
The `cli/` directory is a Cargo workspace.

- `crates/hm/` — the `hm` binary (today's CLI body).
- `crates/hm-exec/` — the `ExecutionBackend` trait + `LocalDockerBackend` (in-process Docker DAG scheduler) + `CloudBackend` (submit+watch over the SDK). The `hm` binary renders the emitted `BuildEvent` stream (via `hm-render`) and owns Ctrl-C; auth is injected (the crate takes a built `HarmontClient`).
- `crates/hm-core/` — the shared core: `config` (layered project/user/env config + credential storage), `exec` (the `ExecutionBackend` trait + `LocalBackend` in-process Docker DAG scheduler + `CloudBackend` submit+watch over the SDK), and `sys_runtime` (the process-wide git/python/dirs/cwd runtime). The `hm` binary renders the emitted `BuildEvent` stream (via `hm-render`) and owns Ctrl-C; auth is injected (the backends take a built `HarmontClient`).
- `crates/hm-render/` — `drive_stream`: consumes an `EventStream` and writes terminal/JSON output. No I/O beyond stdout.
- `crates/hm-pipeline-ir/` — pipeline IR schema (serde structs only, no runtime).
- `crates/hm-common/` — shared utilities (OS/filesystem, formatting, and other cross-crate helpers). This is the source of truth for common code; prefer adding shared helpers here.
Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,11 @@ cargo insta review
| Crate | What it is |
|---|---|
| `crates/hm` | The `hm` binary (package `harmont-cli`) — command-line client for the Harmont CI platform. |
| `crates/hm-exec` | Pluggable CI execution backends: local and cloud. |
| `crates/hm-core` | Shared core: layered configuration/credential storage, the pluggable CI execution backends (local and cloud), and the process-wide system runtime. |
| `crates/hm-vm` | Local Docker backends that run pipeline steps on your machine. |
| `crates/hm-render` | Build-event renderers (human, with progress bars, and JSON). |
| `crates/hm-dsl-engine` | Evaluates Python pipeline definitions; contains the `harmont` Python package (`harmont-py/`). |
| `crates/hm-pipeline-ir` | The pipeline IR wire-format schema. |
| `crates/hm-config` | Layered configuration and credential storage. |
| `crates/hm-plugin-cloud` | Cloud client library. |
| `crates/hm-plugin-protocol` | Wire types shared between `hm` crate internals. |
| `crates/hm-util` | Shared OS and filesystem utilities. |
Expand All @@ -201,7 +200,7 @@ from; a maintainer will regenerate the published docs during review.

Every `hm` command accepts `-v`/`--verbose` for debug-level logging. For
finer control, set `RUST_LOG` with standard tracing filter directives
(for example `RUST_LOG=hm_exec=trace`); when set, it takes precedence over
(for example `RUST_LOG=hm_core::exec=trace`); when set, it takes precedence over
the flag. To see the raw `BuildEvent` stream that the renderers consume,
run with `hm run --format json`.

Expand Down
72 changes: 36 additions & 36 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 3 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,23 @@ resolver = "2"
members = [
"crates/hm",
"crates/hm-common",
"crates/hm-config",
"crates/hm-core",
"crates/hm-plugin-protocol",
"crates/hm-pipeline-ir",
"crates/hm-plugin-cloud",
"crates/hm-dsl-engine",
"crates/hm-render",
"crates/hm-exec",
"crates/hm-vm",
]
default-members = [
"crates/hm",
"crates/hm-common",
"crates/hm-config",
"crates/hm-core",
"crates/hm-plugin-protocol",
"crates/hm-pipeline-ir",
"crates/hm-plugin-cloud",
"crates/hm-dsl-engine",
"crates/hm-render",
"crates/hm-exec",
"crates/hm-vm",
]

Expand All @@ -31,11 +29,10 @@ license = "MIT OR Apache-2.0"
repository = "https://github.com/harmont-dev/harmont-cli"

[workspace.dependencies]
hm-exec = { path = "crates/hm-exec", version = "0.0.0-dev" }
hm-core = { path = "crates/hm-core", version = "0.0.0-dev" }
hm-plugin-protocol = { path = "crates/hm-plugin-protocol", version = "0.0.0-dev" }
hm-plugin-cloud = { path = "crates/hm-plugin-cloud", version = "0.0.0-dev" }
hm-pipeline-ir = { path = "crates/hm-pipeline-ir", version = "0.0.0-dev" }
hm-config = { path = "crates/hm-config", version = "0.0.0-dev" }
hm-dsl-engine = { path = "crates/hm-dsl-engine", version = "0.0.0-dev" }
hm-render = { path = "crates/hm-render", version = "0.0.0-dev" }
hm-vm = { path = "crates/hm-vm", version = "0.0.0-dev" }
Expand Down
4 changes: 2 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ index yet — which `cargo publish --dry-run` would), then publishes the
crates to crates.io in dependency (topological) order:

```
hm-util → hm-pipeline-ir → hm-config → hm-plugin-protocol → hm-render
→ hm-vm → hm-exec → hm-plugin-cloud → hm-dsl-engine → harmont-cli
hm-common → hm-pipeline-ir → hm-plugin-protocol → hm-render
→ hm-vm → hm-core → hm-plugin-cloud → hm-dsl-engine → harmont-cli
```

`harmont-cli` (the `hm` binary) depends on every other crate, so they
Expand Down
3 changes: 0 additions & 3 deletions crates/hm-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ categories = ["command-line-utilities"]
[lib]
path = "src/lib.rs"

[features]
sys-runtime = []

[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
Expand Down
25 changes: 19 additions & 6 deletions crates/hm-common/src/dir_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::path::{Path, PathBuf};
/// accessor instead of reconstructing one per call.
#[derive(Debug, Clone)]
pub struct DirProvider {
home: PathBuf,
config: PathBuf,
cache: PathBuf,
}
Expand All @@ -28,15 +29,23 @@ impl DirProvider {
/// directory.
#[must_use]
pub fn new() -> Option<Self> {
let home = dirs::home_dir()?;
#[cfg(windows)]
let (config, cache) = (dirs::config_dir()?, dirs::cache_dir()?);
#[cfg(not(windows))]
let (config, cache) = {
let home = dirs::home_dir()?;
(home.join(".config"), home.join(".cache"))
};
let (config, cache) = (home.join(".config"), home.join(".cache"));

Some(Self { config, cache })
Some(Self {
home,
config,
cache,
})
}

/// The user home directory.
#[must_use]
pub fn home(&self) -> &Path {
&self.home
}

/// The user configuration root (`~/.config` on non-Windows).
Expand All @@ -63,7 +72,11 @@ mod tests {
let dirs = DirProvider::new().unwrap();
assert!(dirs.config().is_absolute(), "got {:?}", dirs.config());
#[cfg(not(windows))]
assert!(dirs.config().ends_with(".config"), "got {:?}", dirs.config());
assert!(
dirs.config().ends_with(".config"),
"got {:?}",
dirs.config()
);
}

#[rstest]
Expand Down
14 changes: 11 additions & 3 deletions crates/hm-common/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct Git<'bin> {
}

impl<'bin> Git<'bin> {
/// Wrap a `git` executable, typically via `SysRuntime::git()`.
/// Wrap a `git` executable, typically via `AppCtx::git()`.
#[must_use]
pub const fn new(bin: &'bin Path) -> Self {
Self { bin }
Expand Down Expand Up @@ -206,7 +206,11 @@ mod tests {
#[case::wrong_prefix("refs/heads/main", "origin", None)]
#[case::trailing_slash("refs/remotes/origin/", "origin", None)]
#[case::empty("", "origin", None)]
fn parses_default_branch(#[case] line: &str, #[case] remote: &str, #[case] expected: Option<&str>) {
fn parses_default_branch(
#[case] line: &str,
#[case] remote: &str,
#[case] expected: Option<&str>,
) {
let got = parse_default_branch(line.into(), remote);
assert_eq!(got, expected.map(BString::from));
}
Expand Down Expand Up @@ -263,7 +267,11 @@ mod tests {
Command::new("git")
.arg("-C")
.arg(dir.path())
.args(["symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/main"])
.args([
"symbolic-ref",
"refs/remotes/origin/HEAD",
"refs/remotes/origin/main",
])
.captured()
.unwrap()
.success()
Expand Down
2 changes: 0 additions & 2 deletions crates/hm-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! Harmont common utilities shared across the `hm` workspace.

#[cfg(feature = "sys-runtime")]
pub mod sys_runtime;
pub mod dir_provider;
pub mod format;
pub mod fs;
Expand Down
26 changes: 21 additions & 5 deletions crates/hm-common/src/process/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ fn render_capture_error(c: &Captured) -> String {
if !stderr.is_empty() {
const MAX: usize = 2000;
let snippet: String = stderr.chars().take(MAX).collect();
let ellipsis = if snippet.len() < stderr.len() { "…" } else { "" };
let ellipsis = if snippet.len() < stderr.len() {
"…"
} else {
""
};
let _ = write!(msg, ": {snippet}{ellipsis}");
}
msg
Expand Down Expand Up @@ -114,23 +118,35 @@ pub trait CapturedStreams: sealed::Sealed {
}

/// stdout as `&str`, **panicking** if not valid UTF-8.
#[allow(clippy::expect_used, reason = "callers opt into panic-on-invalid by calling _unwrap")]
#[allow(
clippy::expect_used,
reason = "callers opt into panic-on-invalid by calling _unwrap"
)]
fn stdout_str_unwrap(&self) -> &str {
self.stdout_str().expect("stdout was not valid UTF-8")
}
/// stderr as `&str`, **panicking** if not valid UTF-8.
#[allow(clippy::expect_used, reason = "callers opt into panic-on-invalid by calling _unwrap")]
#[allow(
clippy::expect_used,
reason = "callers opt into panic-on-invalid by calling _unwrap"
)]
fn stderr_str_unwrap(&self) -> &str {
self.stderr_str().expect("stderr was not valid UTF-8")
}

/// stdout as an owned `String`, **panicking** if not valid UTF-8.
#[allow(clippy::expect_used, reason = "callers opt into panic-on-invalid by calling _unwrap")]
#[allow(
clippy::expect_used,
reason = "callers opt into panic-on-invalid by calling _unwrap"
)]
fn stdout_string_unwrap(&self) -> String {
self.stdout_string().expect("stdout was not valid UTF-8")
}
/// stderr as an owned `String`, **panicking** if not valid UTF-8.
#[allow(clippy::expect_used, reason = "callers opt into panic-on-invalid by calling _unwrap")]
#[allow(
clippy::expect_used,
reason = "callers opt into panic-on-invalid by calling _unwrap"
)]
fn stderr_string_unwrap(&self) -> String {
self.stderr_string().expect("stderr was not valid UTF-8")
}
Expand Down
5 changes: 4 additions & 1 deletion crates/hm-common/src/process/which.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ mod tests {
fn error_names_the_missing_program() {
let err = pathbin("hm-common-no-such-binary-xyz").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("hm-common-no-such-binary-xyz"), "program: {msg}");
assert!(
msg.contains("hm-common-no-such-binary-xyz"),
"program: {msg}"
);
assert!(msg.contains("not found on PATH"), "phrasing: {msg}");
}
}
2 changes: 1 addition & 1 deletion crates/hm-common/src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct Python<'bin> {
}

impl<'bin> Python<'bin> {
/// Wrap a `python3` executable, typically via `SysRuntime::python()`.
/// Wrap a `python3` executable, typically via `AppCtx::python()`.
#[must_use]
pub const fn new(bin: &'bin Path) -> Self {
Self { bin }
Expand Down
Loading
Loading