use crate::error::HcBundleResult;
use anyhow::Context;
use clap::{Parser, Subcommand};
use holochain_types::dna::DnaBundle;
use holochain_types::prelude::{AppManifest, DnaManifest, ValidatedDnaManifest};
use holochain_types::web_app::WebAppManifest;
use holochain_util::ffs;
use mr_bundle::{FileSystemBundler, Manifest};
use std::path::Path;
use std::path::PathBuf;
pub const DNA_BUNDLE_EXT: &str = "dna";
pub const APP_BUNDLE_EXT: &str = "happ";
pub const WEB_APP_BUNDLE_EXT: &str = "webhapp";
#[derive(Debug, Parser)]
#[command(version, about)]
pub struct HcDnaBundle {
#[command(subcommand)]
pub subcommand: HcDnaBundleSubcommand,
}
#[derive(Debug, Subcommand)]
pub enum HcDnaBundleSubcommand {
Init {
path: PathBuf,
},
Pack {
path: PathBuf,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
},
Unpack {
path: std::path::PathBuf,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
#[arg(short = 'r', long)]
raw: bool,
#[arg(short = 'f', long)]
force: bool,
},
Schema,
Hash {
path: std::path::PathBuf,
},
}
#[derive(Debug, Parser)]
#[command(version, about)]
pub struct HcAppBundle {
#[command(subcommand)]
pub subcommand: HcAppBundleSubcommand,
}
#[derive(Debug, Subcommand)]
pub enum HcAppBundleSubcommand {
Init {
path: PathBuf,
},
Pack {
path: PathBuf,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
#[arg(short, long)]
recursive: bool,
},
Unpack {
path: PathBuf,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
#[arg(short = 'r', long)]
raw: bool,
#[arg(short = 'f', long)]
force: bool,
},
Schema,
}
#[derive(Debug, Parser)]
#[command(version, about)]
pub struct HcWebAppBundle {
#[command(subcommand)]
pub subcommand: HcWebAppBundleSubcommand,
}
#[derive(Debug, Subcommand)]
pub enum HcWebAppBundleSubcommand {
Init {
path: PathBuf,
},
Pack {
path: std::path::PathBuf,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
#[arg(short, long)]
recursive: bool,
},
Unpack {
path: std::path::PathBuf,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
#[arg(short = 'r', long)]
raw: bool,
#[arg(short = 'f', long)]
force: bool,
},
Schema,
}
impl HcDnaBundle {
pub async fn run(self) -> anyhow::Result<()> {
self.subcommand.run().await
}
}
impl HcAppBundle {
pub async fn run(self) -> anyhow::Result<()> {
self.subcommand.run().await
}
}
impl HcWebAppBundle {
pub async fn run(self) -> anyhow::Result<()> {
self.subcommand.run().await
}
}
impl HcDnaBundleSubcommand {
pub async fn run(self) -> anyhow::Result<()> {
match self {
Self::Init { path } => {
crate::init::init_dna(path).await?;
}
Self::Pack { path, output } => {
let name = get_dna_name(&path).await?;
let bundle_path =
crate::packing::pack::<ValidatedDnaManifest>(&path, output, name).await?;
println!("Wrote bundle {}", bundle_path.to_string_lossy());
}
Self::Unpack {
path,
output,
raw,
force,
} => {
let dir_path = if raw {
crate::packing::expand_unknown_bundle(
&path,
DNA_BUNDLE_EXT,
ValidatedDnaManifest::file_name(),
output,
force,
)
.await?
} else {
crate::packing::expand_bundle::<ValidatedDnaManifest>(&path, output, force)
.await?
};
println!("Unpacked to directory {}", dir_path.to_string_lossy());
}
Self::Schema => {
let schema = schemars::schema_for!(DnaManifest);
let schema_string = serde_json::to_string_pretty(&schema)
.context("Failed to pretty print schema")?;
println!("{schema_string}");
}
Self::Hash { path } => {
let bundle = FileSystemBundler::load_from::<ValidatedDnaManifest>(path)
.await
.map(DnaBundle::from)?;
let dna_hash_b64 = bundle.to_dna_file().await?.0.dna_hash().to_string();
println!("{dna_hash_b64}");
}
}
Ok(())
}
}
impl HcAppBundleSubcommand {
pub async fn run(self) -> anyhow::Result<()> {
match self {
Self::Init { path } => {
crate::init::init_app(path).await?;
}
Self::Pack {
path,
output,
recursive,
} => {
let name = get_app_name(&path).await?;
if recursive {
app_pack_recursive(&path).await?;
}
let bundle_path = crate::packing::pack::<AppManifest>(&path, output, name).await?;
println!("Wrote bundle {}", bundle_path.to_string_lossy());
}
Self::Unpack {
path,
output,
raw,
force,
} => {
let dir_path = if raw {
crate::packing::expand_unknown_bundle(
&path,
APP_BUNDLE_EXT,
AppManifest::file_name(),
output,
force,
)
.await?
} else {
crate::packing::expand_bundle::<AppManifest>(&path, output, force).await?
};
println!("Unpacked to directory {}", dir_path.to_string_lossy());
}
Self::Schema => {
let schema = schemars::schema_for!(AppManifest);
let schema_string = serde_json::to_string_pretty(&schema)
.context("Failed to pretty print schema")?;
println!("{schema_string}");
}
}
Ok(())
}
}
impl HcWebAppBundleSubcommand {
pub async fn run(self) -> anyhow::Result<()> {
match self {
Self::Init { path } => {
crate::init::init_web_app(path).await?;
}
Self::Pack {
path,
output,
recursive,
} => {
let name = get_web_app_name(&path).await?;
if recursive {
web_app_pack_recursive(&path).await?;
}
let bundle_path =
crate::packing::pack::<WebAppManifest>(&path, output, name).await?;
println!("Wrote bundle {}", bundle_path.to_string_lossy());
}
Self::Unpack {
path,
output,
raw,
force,
} => {
let dir_path = if raw {
crate::packing::expand_unknown_bundle(
&path,
WEB_APP_BUNDLE_EXT,
WebAppManifest::file_name(),
output,
force,
)
.await?
} else {
crate::packing::expand_bundle::<WebAppManifest>(&path, output, force).await?
};
println!("Unpacked to directory {}", dir_path.to_string_lossy());
}
Self::Schema => {
let schema = schemars::schema_for!(WebAppManifest);
let schema_string = serde_json::to_string_pretty(&schema)
.context("Failed to pretty print schema")?;
println!("{schema_string}");
}
}
Ok(())
}
}
pub async fn get_dna_name(manifest_path: &Path) -> HcBundleResult<String> {
let manifest_path = manifest_path.to_path_buf();
let manifest_path = manifest_path.join(ValidatedDnaManifest::file_name());
let manifest_yaml = ffs::read_to_string(&manifest_path).await?;
let manifest: DnaManifest = serde_yaml::from_str(&manifest_yaml)?;
Ok(manifest.name())
}
pub async fn get_app_name(manifest_path: &Path) -> HcBundleResult<String> {
let manifest_path = manifest_path.to_path_buf();
let manifest_path = manifest_path.join(AppManifest::file_name());
let manifest_yaml = ffs::read_to_string(&manifest_path).await?;
let manifest: AppManifest = serde_yaml::from_str(&manifest_yaml)?;
Ok(manifest.app_name().to_string())
}
pub async fn get_web_app_name(manifest_path: &Path) -> HcBundleResult<String> {
let manifest_path = manifest_path.to_path_buf();
let manifest_path = manifest_path.join(WebAppManifest::file_name());
let manifest_yaml = ffs::read_to_string(&manifest_path).await?;
let manifest: WebAppManifest = serde_yaml::from_str(&manifest_yaml)?;
Ok(manifest.app_name().to_string())
}
pub async fn web_app_pack_recursive(web_app_workdir_path: &PathBuf) -> anyhow::Result<()> {
let canonical_web_app_workdir_path = ffs::canonicalize(web_app_workdir_path).await?;
let web_app_manifest_path = canonical_web_app_workdir_path.join(WebAppManifest::file_name());
let web_app_manifest: WebAppManifest =
serde_yaml::from_reader(std::fs::File::open(&web_app_manifest_path)?)?;
let app_bundle_location = web_app_manifest.happ_bundle_location();
let mut bundled_app_location = PathBuf::from(app_bundle_location);
bundled_app_location.pop();
let app_workdir_location = PathBuf::new()
.join(web_app_workdir_path)
.join(bundled_app_location);
HcAppBundleSubcommand::Pack {
path: ffs::canonicalize(app_workdir_location).await?,
output: None,
recursive: true,
}
.run()
.await?;
Ok(())
}
pub async fn app_pack_recursive(app_workdir_path: &PathBuf) -> anyhow::Result<()> {
let app_workdir_path = ffs::canonicalize(app_workdir_path).await?;
let app_manifest_path = app_workdir_path.join(AppManifest::file_name());
let f = std::fs::File::open(&app_manifest_path)?;
let manifest: AppManifest = serde_yaml::from_reader(f)?;
let dnas_workdir_locations =
bundled_dnas_workdir_locations(&app_manifest_path, &manifest).await?;
for dna_workdir_location in dnas_workdir_locations {
HcDnaBundleSubcommand::Pack {
path: dna_workdir_location,
output: None,
}
.run()
.await?;
}
Ok(())
}
pub async fn bundled_dnas_workdir_locations(
app_manifest_path: &Path,
app_manifest: &AppManifest,
) -> anyhow::Result<Vec<PathBuf>> {
let mut dna_locations: Vec<PathBuf> = vec![];
let mut app_workdir_location = app_manifest_path.to_path_buf();
app_workdir_location.pop();
for app_role in app_manifest.app_roles() {
if let Some(file) = app_role.dna.path {
let mut dna_bundle_location = PathBuf::from(file);
dna_bundle_location.pop();
let dna_workdir_location = PathBuf::new()
.join(&app_workdir_location)
.join(&dna_bundle_location);
dna_locations.push(ffs::canonicalize(dna_workdir_location).await?);
}
}
Ok(dna_locations)
}