feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
|
|
|
|
use anyhow::Context;
|
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
struct Manifest {
|
|
|
|
|
package: PackageMeta,
|
|
|
|
|
runtime: RuntimeMeta,
|
|
|
|
|
ui: UiMeta,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
struct PackageMeta {
|
|
|
|
|
id: String,
|
|
|
|
|
name: String,
|
|
|
|
|
version: String,
|
|
|
|
|
description: Option<String>,
|
|
|
|
|
author: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
struct RuntimeMeta {
|
|
|
|
|
module: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
struct UiMeta {
|
|
|
|
|
entry: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
|
|
|
let args: Vec<String> = std::env::args().collect();
|
|
|
|
|
|
|
|
|
|
match args.get(1).map(String::as_str) {
|
|
|
|
|
Some("check") => {
|
|
|
|
|
let dir = args.get(2).context("usage: weft-pack check <dir>")?;
|
|
|
|
|
let result = check_package(Path::new(dir))?;
|
|
|
|
|
println!("{result}");
|
|
|
|
|
}
|
|
|
|
|
Some("info") => {
|
|
|
|
|
let dir = args.get(2).context("usage: weft-pack info <dir>")?;
|
|
|
|
|
let manifest = load_manifest(Path::new(dir))?;
|
|
|
|
|
print_info(&manifest);
|
|
|
|
|
}
|
2026-03-11 08:45:31 +00:00
|
|
|
Some("install") => {
|
|
|
|
|
let dir = args.get(2).context("usage: weft-pack install <dir>")?;
|
|
|
|
|
install_package(Path::new(dir))?;
|
|
|
|
|
}
|
2026-03-11 08:54:39 +00:00
|
|
|
Some("uninstall") => {
|
|
|
|
|
let app_id = args.get(2).context("usage: weft-pack uninstall <app_id>")?;
|
|
|
|
|
uninstall_package(app_id)?;
|
|
|
|
|
}
|
feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
_ => {
|
|
|
|
|
eprintln!("usage:");
|
2026-03-11 08:54:39 +00:00
|
|
|
eprintln!(" weft-pack check <dir> validate a package directory");
|
|
|
|
|
eprintln!(" weft-pack info <dir> print package metadata");
|
|
|
|
|
eprintln!(" weft-pack install <dir> install package to app store");
|
|
|
|
|
eprintln!(" weft-pack uninstall <app_id> remove installed package");
|
feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_package(dir: &Path) -> anyhow::Result<String> {
|
|
|
|
|
let mut errors: Vec<String> = Vec::new();
|
|
|
|
|
|
|
|
|
|
let manifest = match load_manifest(dir) {
|
|
|
|
|
Ok(m) => Some(m),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
errors.push(format!("wapp.toml: {e}"));
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(ref m) = manifest {
|
|
|
|
|
if !is_valid_app_id(&m.package.id) {
|
|
|
|
|
errors.push(format!(
|
|
|
|
|
"package.id '{}' does not match required pattern",
|
|
|
|
|
m.package.id
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if m.package.name.is_empty() {
|
|
|
|
|
errors.push("package.name is empty".into());
|
|
|
|
|
}
|
|
|
|
|
if m.package.name.len() > 64 {
|
|
|
|
|
errors.push(format!(
|
|
|
|
|
"package.name exceeds 64 characters ({})",
|
|
|
|
|
m.package.name.len()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let wasm_path = dir.join(&m.runtime.module);
|
|
|
|
|
if !wasm_path.exists() {
|
|
|
|
|
errors.push(format!(
|
|
|
|
|
"runtime.module '{}' not found",
|
|
|
|
|
wasm_path.display()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ui_path = dir.join(&m.ui.entry);
|
|
|
|
|
if !ui_path.exists() {
|
|
|
|
|
errors.push(format!("ui.entry '{}' not found", ui_path.display()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if errors.is_empty() {
|
|
|
|
|
Ok("OK".into())
|
|
|
|
|
} else {
|
|
|
|
|
Err(anyhow::anyhow!("{}", errors.join("\n")))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_manifest(dir: &Path) -> anyhow::Result<Manifest> {
|
|
|
|
|
let manifest_path = dir.join("wapp.toml");
|
|
|
|
|
let text = std::fs::read_to_string(&manifest_path)
|
|
|
|
|
.with_context(|| format!("read {}", manifest_path.display()))?;
|
|
|
|
|
toml::from_str(&text).with_context(|| format!("parse {}", manifest_path.display()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn print_info(m: &Manifest) {
|
|
|
|
|
println!("id: {}", m.package.id);
|
|
|
|
|
println!("name: {}", m.package.name);
|
|
|
|
|
println!("version: {}", m.package.version);
|
|
|
|
|
if let Some(ref d) = m.package.description {
|
|
|
|
|
println!("desc: {d}");
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref a) = m.package.author {
|
|
|
|
|
println!("author: {a}");
|
|
|
|
|
}
|
|
|
|
|
println!("module: {}", m.runtime.module);
|
|
|
|
|
println!("ui: {}", m.ui.entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_valid_app_id(id: &str) -> bool {
|
|
|
|
|
let parts: Vec<&str> = id.split('.').collect();
|
|
|
|
|
if parts.len() < 3 {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
parts.iter().all(|p| {
|
|
|
|
|
!p.is_empty()
|
|
|
|
|
&& p.chars()
|
|
|
|
|
.next()
|
|
|
|
|
.map(|c| c.is_ascii_lowercase())
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
&& p.chars()
|
|
|
|
|
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 08:45:31 +00:00
|
|
|
fn resolve_install_root() -> anyhow::Result<PathBuf> {
|
feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
if let Ok(explicit) = std::env::var("WEFT_APP_STORE") {
|
2026-03-11 08:45:31 +00:00
|
|
|
return Ok(PathBuf::from(explicit));
|
feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
}
|
|
|
|
|
if let Ok(home) = std::env::var("HOME") {
|
2026-03-11 08:45:31 +00:00
|
|
|
return Ok(PathBuf::from(home)
|
|
|
|
|
.join(".local")
|
|
|
|
|
.join("share")
|
|
|
|
|
.join("weft")
|
|
|
|
|
.join("apps"));
|
|
|
|
|
}
|
|
|
|
|
anyhow::bail!("cannot determine install root: HOME and WEFT_APP_STORE are both unset")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn install_package(dir: &Path) -> anyhow::Result<()> {
|
2026-03-11 08:54:39 +00:00
|
|
|
let root = resolve_install_root()?;
|
|
|
|
|
install_package_to(dir, &root)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn install_package_to(dir: &Path, store_root: &Path) -> anyhow::Result<()> {
|
2026-03-11 08:45:31 +00:00
|
|
|
check_package(dir)?;
|
|
|
|
|
let manifest = load_manifest(dir)?;
|
|
|
|
|
let app_id = &manifest.package.id;
|
|
|
|
|
let dest = store_root.join(app_id);
|
|
|
|
|
if dest.exists() {
|
|
|
|
|
anyhow::bail!(
|
|
|
|
|
"package '{}' is already installed at {}; remove it first",
|
|
|
|
|
app_id,
|
|
|
|
|
dest.display()
|
feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
);
|
|
|
|
|
}
|
2026-03-11 08:45:31 +00:00
|
|
|
copy_dir(dir, &dest)
|
|
|
|
|
.with_context(|| format!("copy {} -> {}", dir.display(), dest.display()))?;
|
|
|
|
|
println!("installed {} -> {}", app_id, dest.display());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 08:54:39 +00:00
|
|
|
fn uninstall_package(app_id: &str) -> anyhow::Result<()> {
|
|
|
|
|
let root = resolve_install_root()?;
|
|
|
|
|
uninstall_package_from(app_id, &root)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn uninstall_package_from(app_id: &str, store_root: &Path) -> anyhow::Result<()> {
|
|
|
|
|
if !is_valid_app_id(app_id) {
|
|
|
|
|
anyhow::bail!("'{}' is not a valid app ID", app_id);
|
|
|
|
|
}
|
|
|
|
|
let target = store_root.join(app_id);
|
|
|
|
|
if !target.exists() {
|
|
|
|
|
anyhow::bail!(
|
|
|
|
|
"package '{}' is not installed at {}",
|
|
|
|
|
app_id,
|
|
|
|
|
target.display()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
std::fs::remove_dir_all(&target).with_context(|| format!("remove {}", target.display()))?;
|
|
|
|
|
println!("uninstalled {}", app_id);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 08:45:31 +00:00
|
|
|
fn copy_dir(src: &Path, dst: &Path) -> anyhow::Result<()> {
|
|
|
|
|
std::fs::create_dir_all(dst)?;
|
|
|
|
|
for entry in std::fs::read_dir(src)? {
|
|
|
|
|
let entry = entry?;
|
|
|
|
|
let src_path = entry.path();
|
|
|
|
|
let dst_path = dst.join(entry.file_name());
|
|
|
|
|
if src_path.is_dir() {
|
|
|
|
|
copy_dir(&src_path, &dst_path)?;
|
|
|
|
|
} else {
|
|
|
|
|
std::fs::copy(&src_path, &dst_path)
|
|
|
|
|
.with_context(|| format!("copy {}", src_path.display()))?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn app_id_valid() {
|
|
|
|
|
assert!(is_valid_app_id("com.example.notes"));
|
|
|
|
|
assert!(is_valid_app_id("org.weft.calculator"));
|
|
|
|
|
assert!(is_valid_app_id("io.github.username.app"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn app_id_invalid() {
|
|
|
|
|
assert!(!is_valid_app_id("com.example"));
|
|
|
|
|
assert!(!is_valid_app_id("Com.example.notes"));
|
|
|
|
|
assert!(!is_valid_app_id("com.example.notes-app"));
|
|
|
|
|
assert!(!is_valid_app_id("com..example.notes"));
|
|
|
|
|
assert!(!is_valid_app_id(""));
|
|
|
|
|
assert!(!is_valid_app_id("com.Example.notes"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn check_package_missing_manifest() {
|
|
|
|
|
let tmp = std::env::temp_dir().join("weft_pack_test_empty");
|
|
|
|
|
let _ = std::fs::create_dir_all(&tmp);
|
|
|
|
|
let result = check_package(&tmp);
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
let _ = std::fs::remove_dir_all(&tmp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn check_package_valid() {
|
|
|
|
|
use std::fs;
|
|
|
|
|
let tmp = std::env::temp_dir().join("weft_pack_test_valid");
|
|
|
|
|
let ui_dir = tmp.join("ui");
|
|
|
|
|
let _ = fs::create_dir_all(&ui_dir);
|
|
|
|
|
fs::write(tmp.join("app.wasm"), b"\0asm\x01\0\0\0").unwrap();
|
|
|
|
|
fs::write(ui_dir.join("index.html"), b"<!DOCTYPE html>").unwrap();
|
|
|
|
|
fs::write(
|
|
|
|
|
tmp.join("wapp.toml"),
|
|
|
|
|
r#"
|
|
|
|
|
[package]
|
|
|
|
|
id = "com.example.test"
|
|
|
|
|
name = "Test App"
|
|
|
|
|
version = "1.0.0"
|
|
|
|
|
|
|
|
|
|
[runtime]
|
|
|
|
|
module = "app.wasm"
|
|
|
|
|
|
|
|
|
|
[ui]
|
|
|
|
|
entry = "ui/index.html"
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let result = check_package(&tmp);
|
|
|
|
|
assert!(result.is_ok(), "{result:?}");
|
|
|
|
|
assert_eq!(result.unwrap(), "OK");
|
|
|
|
|
|
|
|
|
|
let _ = fs::remove_dir_all(&tmp);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 08:54:39 +00:00
|
|
|
#[test]
|
|
|
|
|
fn install_package_copies_to_store() {
|
|
|
|
|
use std::fs;
|
|
|
|
|
let id = format!("weft.pack.install{}", std::process::id());
|
|
|
|
|
let src = std::env::temp_dir().join(format!("weft_pack_install_src_{}", id));
|
|
|
|
|
let store = std::env::temp_dir().join(format!("weft_pack_install_store_{}", id));
|
|
|
|
|
let ui_dir = src.join("ui");
|
|
|
|
|
let _ = fs::create_dir_all(&ui_dir);
|
|
|
|
|
fs::write(src.join("app.wasm"), b"\0asm").unwrap();
|
|
|
|
|
fs::write(ui_dir.join("index.html"), b"<!DOCTYPE html>").unwrap();
|
|
|
|
|
let app_id = format!("com.example.t{}", std::process::id());
|
|
|
|
|
fs::write(
|
|
|
|
|
src.join("wapp.toml"),
|
|
|
|
|
format!(
|
|
|
|
|
"[package]\nid = \"{app_id}\"\nname = \"Test\"\nversion = \"1.0.0\"\n\n\
|
|
|
|
|
[runtime]\nmodule = \"app.wasm\"\n\n[ui]\nentry = \"ui/index.html\"\n"
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let result = install_package_to(&src, &store);
|
|
|
|
|
assert!(result.is_ok(), "{result:?}");
|
|
|
|
|
assert!(store.join(&app_id).join("app.wasm").exists());
|
|
|
|
|
assert!(store.join(&app_id).join("wapp.toml").exists());
|
|
|
|
|
assert!(store.join(&app_id).join("ui").join("index.html").exists());
|
|
|
|
|
assert!(install_package_to(&src, &store).is_err());
|
|
|
|
|
let _ = fs::remove_dir_all(&src);
|
|
|
|
|
let _ = fs::remove_dir_all(&store);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn uninstall_package_removes_directory() {
|
|
|
|
|
use std::fs;
|
|
|
|
|
let id = format!("weft.pack.uninstall{}", std::process::id());
|
|
|
|
|
let src = std::env::temp_dir().join(format!("weft_pack_uninstall_src_{}", id));
|
|
|
|
|
let store = std::env::temp_dir().join(format!("weft_pack_uninstall_store_{}", id));
|
|
|
|
|
let ui_dir = src.join("ui");
|
|
|
|
|
let _ = fs::create_dir_all(&ui_dir);
|
|
|
|
|
fs::write(src.join("app.wasm"), b"\0asm").unwrap();
|
|
|
|
|
fs::write(ui_dir.join("index.html"), b"").unwrap();
|
|
|
|
|
let app_id = format!("com.example.u{}", std::process::id());
|
|
|
|
|
fs::write(
|
|
|
|
|
src.join("wapp.toml"),
|
|
|
|
|
format!(
|
|
|
|
|
"[package]\nid = \"{app_id}\"\nname = \"U\"\nversion = \"1.0.0\"\n\n\
|
|
|
|
|
[runtime]\nmodule = \"app.wasm\"\n\n[ui]\nentry = \"ui/index.html\"\n"
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
install_package_to(&src, &store).unwrap();
|
|
|
|
|
assert!(store.join(&app_id).exists());
|
|
|
|
|
let result = uninstall_package_from(&app_id, &store);
|
|
|
|
|
assert!(result.is_ok(), "{result:?}");
|
|
|
|
|
assert!(!store.join(&app_id).exists());
|
|
|
|
|
assert!(uninstall_package_from(&app_id, &store).is_err());
|
|
|
|
|
let _ = fs::remove_dir_all(&src);
|
|
|
|
|
let _ = fs::remove_dir_all(&store);
|
|
|
|
|
}
|
|
|
|
|
|
feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.
src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
package.name is non-empty and <=64 chars, confirms runtime.module and
ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
each starting with a lowercase letter, digits allowed, no hyphens or
uppercase).
Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.
New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 08:40:34 +00:00
|
|
|
#[test]
|
|
|
|
|
fn check_package_invalid_app_id() {
|
|
|
|
|
use std::fs;
|
|
|
|
|
let tmp = std::env::temp_dir().join("weft_pack_test_invalid_id");
|
|
|
|
|
let ui_dir = tmp.join("ui");
|
|
|
|
|
let _ = fs::create_dir_all(&ui_dir);
|
|
|
|
|
fs::write(tmp.join("app.wasm"), b"\0asm").unwrap();
|
|
|
|
|
fs::write(ui_dir.join("index.html"), b"").unwrap();
|
|
|
|
|
fs::write(
|
|
|
|
|
tmp.join("wapp.toml"),
|
|
|
|
|
r#"
|
|
|
|
|
[package]
|
|
|
|
|
id = "bad-id"
|
|
|
|
|
name = "Bad"
|
|
|
|
|
version = "0.1.0"
|
|
|
|
|
|
|
|
|
|
[runtime]
|
|
|
|
|
module = "app.wasm"
|
|
|
|
|
|
|
|
|
|
[ui]
|
|
|
|
|
entry = "ui/index.html"
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let result = check_package(&tmp);
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
let _ = fs::remove_dir_all(&tmp);
|
|
|
|
|
}
|
|
|
|
|
}
|