mirror of
https://github.com/marcoallegretti/WEFT_OS.git
synced 2026-03-26 17:03:09 +00:00
feat(pack): bundle and unbundle subcommands for dist packaging
Add bundle <dir> [--out <dir>] and unbundle <archive> [--out <dir>] subcommands to weft-pack. bundle: validates the package, reads app_id from wapp.toml, writes <app_id>.app.tar.zst to the output directory (default: current dir). Archive root is <app_id>/ so extraction reproduces the package directory. Fails if the archive already exists. unbundle: decompresses and extracts a .app.tar.zst into the output directory (default: current dir). Compression level 0 (zstd default). No symlinks followed. Dependencies added: tar 0.4, zstd 0.13. Test: bundle_and_unbundle_roundtrip.
This commit is contained in:
parent
98a21da734
commit
12fa53a585
2 changed files with 91 additions and 0 deletions
|
|
@ -16,3 +16,5 @@ ed25519-dalek = { version = "2", features = ["rand_core"] }
|
|||
sha2 = "0.10"
|
||||
rand = "0.8"
|
||||
hex = "0.4"
|
||||
tar = "0.4"
|
||||
zstd = "0.13"
|
||||
|
|
|
|||
|
|
@ -55,6 +55,23 @@ fn main() -> anyhow::Result<()> {
|
|||
Some("list") => {
|
||||
list_installed();
|
||||
}
|
||||
Some("bundle") => {
|
||||
let dir = args.get(2).context("usage: weft-pack bundle <dir>")?;
|
||||
let out = args
|
||||
.windows(2)
|
||||
.find(|w| w[0] == "--out")
|
||||
.map(|w| w[1].as_str());
|
||||
bundle_package(Path::new(dir), out.map(Path::new))?;
|
||||
}
|
||||
Some("unbundle") => {
|
||||
let archive = args.get(2).context("usage: weft-pack unbundle <archive>")?;
|
||||
let out = args
|
||||
.windows(2)
|
||||
.find(|w| w[0] == "--out")
|
||||
.map(|w| w[1].as_str())
|
||||
.unwrap_or(".");
|
||||
unbundle_package(Path::new(archive), Path::new(out))?;
|
||||
}
|
||||
Some("generate-key") => {
|
||||
let out = args.get(2).map(String::as_str).unwrap_or(".");
|
||||
generate_key(Path::new(out))?;
|
||||
|
|
@ -94,6 +111,8 @@ fn main() -> anyhow::Result<()> {
|
|||
eprintln!(" weft-pack install <dir> install package to app store");
|
||||
eprintln!(" weft-pack uninstall <app_id> remove installed package");
|
||||
eprintln!(" weft-pack list list installed packages");
|
||||
eprintln!(" weft-pack bundle <dir> [--out <dir>] create .app.tar.zst archive");
|
||||
eprintln!(" weft-pack unbundle <archive> [--out <dir>] extract .app.tar.zst");
|
||||
eprintln!(" weft-pack generate-key [<outdir>] generate Ed25519 keypair");
|
||||
eprintln!(" weft-pack sign <dir> --key <key> sign package with private key");
|
||||
eprintln!(" weft-pack verify <dir> --key <pub> verify package signature");
|
||||
|
|
@ -338,6 +357,40 @@ fn copy_dir(src: &Path, dst: &Path) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn bundle_package(dir: &Path, out_dir: Option<&Path>) -> anyhow::Result<()> {
|
||||
let manifest = load_manifest(dir)?;
|
||||
let app_id = &manifest.package.id;
|
||||
let archive_name = format!("{app_id}.app.tar.zst");
|
||||
let dest_dir = out_dir.unwrap_or_else(|| Path::new("."));
|
||||
let archive_path = dest_dir.join(&archive_name);
|
||||
if archive_path.exists() {
|
||||
anyhow::bail!("{} already exists", archive_path.display());
|
||||
}
|
||||
let file = std::fs::File::create(&archive_path)
|
||||
.with_context(|| format!("create {}", archive_path.display()))?;
|
||||
let encoder = zstd::Encoder::new(file, 0)
|
||||
.context("create zstd encoder")?
|
||||
.auto_finish();
|
||||
let mut tar = tar::Builder::new(encoder);
|
||||
tar.follow_symlinks(false);
|
||||
tar.append_dir_all(app_id, dir)
|
||||
.with_context(|| format!("append {} to archive", dir.display()))?;
|
||||
tar.finish().context("finish tar archive")?;
|
||||
println!("bundled: {}", archive_path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unbundle_package(archive: &Path, out_dir: &Path) -> anyhow::Result<()> {
|
||||
let file =
|
||||
std::fs::File::open(archive).with_context(|| format!("open {}", archive.display()))?;
|
||||
let decoder = zstd::Decoder::new(file).context("create zstd decoder")?;
|
||||
let mut tar = tar::Archive::new(decoder);
|
||||
tar.unpack(out_dir)
|
||||
.with_context(|| format!("unpack to {}", out_dir.display()))?;
|
||||
println!("unbundled: {} -> {}", archive.display(), out_dir.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_bundle_files(
|
||||
base: &Path,
|
||||
dir: &Path,
|
||||
|
|
@ -678,6 +731,42 @@ entry = "ui/index.html"
|
|||
let _ = fs::remove_dir_all(&tmp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bundle_and_unbundle_roundtrip() {
|
||||
use std::fs;
|
||||
let id = format!("bundle_{}", std::process::id());
|
||||
let src = std::env::temp_dir().join(&id);
|
||||
let out = std::env::temp_dir().join(format!("{id}_out"));
|
||||
let ui = src.join("ui");
|
||||
let _ = fs::create_dir_all(&ui);
|
||||
fs::write(src.join("app.wasm"), b"\0asm\x01\0\0\0").unwrap();
|
||||
fs::write(ui.join("index.html"), b"<!DOCTYPE html>").unwrap();
|
||||
let app_id = format!("com.example.b{}", std::process::id());
|
||||
fs::write(
|
||||
src.join("wapp.toml"),
|
||||
format!(
|
||||
"[package]\nid = \"{app_id}\"\nname = \"B\"\nversion = \"1.0.0\"\n\n\
|
||||
[runtime]\nmodule = \"app.wasm\"\n\n[ui]\nentry = \"ui/index.html\"\n"
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = fs::create_dir_all(&out);
|
||||
bundle_package(&src, Some(&out)).unwrap();
|
||||
let archive = out.join(format!("{app_id}.app.tar.zst"));
|
||||
assert!(archive.exists());
|
||||
|
||||
let unpack = std::env::temp_dir().join(format!("{id}_unpack"));
|
||||
let _ = fs::create_dir_all(&unpack);
|
||||
unbundle_package(&archive, &unpack).unwrap();
|
||||
assert!(unpack.join(&app_id).join("app.wasm").exists());
|
||||
assert!(unpack.join(&app_id).join("ui").join("index.html").exists());
|
||||
|
||||
let _ = fs::remove_dir_all(&src);
|
||||
let _ = fs::remove_dir_all(&out);
|
||||
let _ = fs::remove_dir_all(&unpack);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_and_verify_roundtrip() {
|
||||
use std::fs;
|
||||
|
|
|
|||
Loading…
Reference in a new issue