diff --git a/crates/aura-cli/src/scaffold.rs b/crates/aura-cli/src/scaffold.rs index ceb8385..a3dedaf 100644 --- a/crates/aura-cli/src/scaffold.rs +++ b/crates/aura-cli/src/scaffold.rs @@ -283,7 +283,9 @@ pub fn scaffold_project(spec: &ProjectScaffoldSpec) -> Result<(), String> { return Ok(()); } match std::process::Command::new("git").arg("init").current_dir(&spec.target_dir).output() { - Ok(o) if o.status.success() => {} + Ok(o) if o.status.success() => { + commit_all(&spec.target_dir, "aura new scaffold"); + } _ => eprintln!("aura: warning: git init failed (project created without a repo)"), } Ok(()) @@ -322,7 +324,9 @@ pub fn scaffold_node_crate(spec: &ScaffoldSpec) -> Result<(), String> { return Ok(()); } match std::process::Command::new("git").arg("init").current_dir(&spec.target_dir).output() { - Ok(o) if o.status.success() => {} + Ok(o) if o.status.success() => { + commit_all(&spec.target_dir, "aura nodes new scaffold"); + } _ => eprintln!("aura: warning: git init failed (node crate created without a repo)"), } Ok(()) @@ -344,6 +348,41 @@ pub fn append_nodes_pointer(project_root: &Path, pointer: &str) -> Result<(), St .map_err(|e| format!("writing `{}`: {e}", path.display())) } +/// Stage everything and make the initial commit in a freshly-`git init`ed +/// scaffold, so `git rev-parse HEAD` resolves from the very first run +/// (otherwise HEAD stays unborn and provenance stamping sees no commit). A +/// committer identity is passed inline via `-c` so this works on a box with +/// no global git config; best-effort — a failure is a warning, never fatal, +/// and never runs when `git init` itself was skipped or failed. +fn commit_all(dir: &std::path::Path, message: &str) { + let add = std::process::Command::new("git") + .args(["add", "-A"]) + .current_dir(dir) + .output(); + if !matches!(add, Ok(o) if o.status.success()) { + eprintln!("aura: warning: git add failed (scaffold created without an initial commit)"); + return; + } + let commit = std::process::Command::new("git") + .args([ + "-c", + "user.email=aura@localhost", + "-c", + "user.name=aura", + "commit", + "-q", + "-m", + message, + ]) + .current_dir(dir) + .output(); + if !matches!(commit, Ok(o) if o.status.success()) { + eprintln!( + "aura: warning: git commit failed (scaffold created without an initial commit)" + ); + } +} + /// Whether `dir` already sits inside a git work tree (`git rev-parse`, the /// same oracle cargo-new consults). A missing git binary or any failure reads /// as "not in a work tree" — the init stays best-effort either way. diff --git a/crates/aura-cli/tests/project_nodes.rs b/crates/aura-cli/tests/project_nodes.rs index 1b2a618..cccba91 100644 --- a/crates/aura-cli/tests/project_nodes.rs +++ b/crates/aura-cli/tests/project_nodes.rs @@ -37,6 +37,34 @@ fn nodes_new_scaffolds_a_sibling_crate_and_attaches_it() { assert!(toml.contains("../lab-nodes"), "{toml}"); } +/// Property (#243): a freshly scaffolded node crate stamps resolvable +/// provenance too — `aura nodes new` follows the same `git init` with an +/// initial commit as `aura new` (`scaffold_node_crate`, `scaffold.rs`), so +/// `git rev-parse HEAD` resolves from the crate's very first run rather than +/// leaving HEAD unborn. This half of #243's property runs and is asserted +/// separately from `new_outside_a_work_tree_leaves_a_resolvable_head` +/// (`project_new.rs`), which covers the `aura new` project half only. +#[test] +fn nodes_new_leaves_a_resolvable_head() { + let cwd = temp_cwd("resolvable-head"); + assert!(aura(&["new", "lab"], &cwd).status.success()); + let proj = cwd.join("lab"); + let engine = env!("CARGO_MANIFEST_DIR").to_string() + "/../.."; + let out = aura(&["nodes", "new", "lab-nodes", "--engine-path", &engine], &proj); + assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr)); + let crate_dir = cwd.join("lab-nodes"); + let head = Command::new("git") + .args(["rev-parse", "HEAD"]) + .current_dir(&crate_dir) + .output() + .expect("run git rev-parse HEAD"); + assert!( + head.status.success(), + "fresh node-crate scaffold has no HEAD (`git rev-parse HEAD` failed): {}", + String::from_utf8_lossy(&head.stderr) + ); +} + /// Property (#241): `--namespace` sets the vocabulary prefix independently /// of the crate's own (directory) name — a crate can be named anything, but /// the blueprints that reference its nodes address them under the namespace