greentic_component/cmd/
hash.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context, Result};
5use clap::{Args, Parser};
6use serde_json::Value;
7
8#[derive(Args, Debug, Clone)]
9#[command(about = "Recompute the wasm hash inside component.manifest.json")]
10pub struct HashArgs {
11    /// Path to component.manifest.json
12    #[arg(default_value = "component.manifest.json")]
13    pub manifest: PathBuf,
14    /// Optional override for the wasm artifact path
15    #[arg(long)]
16    pub wasm: Option<PathBuf>,
17}
18
19#[derive(Parser, Debug)]
20struct HashCli {
21    #[command(flatten)]
22    args: HashArgs,
23}
24
25pub fn parse_from_cli() -> HashArgs {
26    HashCli::parse().args
27}
28
29pub fn run(args: HashArgs) -> Result<()> {
30    let manifest_path = args.manifest;
31    let manifest_text = fs::read_to_string(&manifest_path)
32        .with_context(|| format!("failed to read {}", manifest_path.display()))?;
33    let mut manifest: Value = serde_json::from_str(&manifest_text)
34        .with_context(|| format!("invalid json: {}", manifest_path.display()))?;
35    let wasm_path = resolve_wasm_path(&manifest, args.wasm.as_deref(), &manifest_path)?;
36    let wasm_bytes = fs::read(&wasm_path)
37        .with_context(|| format!("failed to read wasm at {}", wasm_path.display()))?;
38    let digest = blake3::hash(&wasm_bytes).to_hex().to_string();
39    manifest["hashes"]["component_wasm"] = Value::String(format!("blake3:{digest}"));
40    let formatted = serde_json::to_string_pretty(&manifest)?;
41    fs::write(&manifest_path, formatted + "\n")
42        .with_context(|| format!("failed to write {}", manifest_path.display()))?;
43    println!(
44        "Updated {} with hash of {}",
45        manifest_path.display(),
46        wasm_path.display()
47    );
48    Ok(())
49}
50
51fn resolve_wasm_path(
52    manifest: &Value,
53    override_path: Option<&Path>,
54    manifest_path: &Path,
55) -> Result<PathBuf> {
56    if let Some(path) = override_path {
57        return Ok(path.to_path_buf());
58    }
59    let artifact = manifest
60        .get("artifacts")
61        .and_then(|art| art.get("component_wasm"))
62        .and_then(Value::as_str)
63        .ok_or_else(|| anyhow::anyhow!("manifest is missing artifacts.component_wasm"))?;
64    let root = manifest_path
65        .parent()
66        .map(Path::to_path_buf)
67        .unwrap_or_else(|| PathBuf::from("."));
68    Ok(root.join(artifact))
69}