Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ rust = [
{ version = "nightly", components = "rust-src,rustfmt", targets = "wasm32-unknown-unknown" },
]
typos = "latest"
uv = "latest"
uv = "0.11.8"
yq = "latest"
zig = "latest"

Expand Down Expand Up @@ -157,15 +157,15 @@ description = "Build the har1 workflow WASM module"
dir = "services/ws-modules/har1"
run = """
wasm-pack build . --target web
yq eval-all -i 'select(fileIndex == 0) * select(fileIndex == 1)' pkg/package.json package.json
cargo run -p module-manifest-to-package-json
"""

[tasks.build-ws-face-detection-module]
description = "Build the face detection workflow WASM module"
dir = "services/ws-modules/face-detection"
run = """
wasm-pack build . --target web
yq eval-all -i 'select(fileIndex == 0) * select(fileIndex == 1)' pkg/package.json package.json
cargo run -p module-manifest-to-package-json
"""

[tasks.build-ws-comm1-module]
Expand Down Expand Up @@ -234,15 +234,15 @@ description = "Build the pydata1 Python workflow module"
dir = "services/ws-modules/pydata1"
run = """
uv build --wheel --out-dir pkg
cargo run -p pyproject-to-package-json
cargo run -p module-manifest-to-package-json
"""

[tasks.build-ws-pyface1-module]
description = "Build the pyface1 Python face detection workflow module"
dir = "services/ws-modules/pyface1"
run = """
uv build --wheel --out-dir pkg
cargo run -p pyproject-to-package-json
cargo run -p module-manifest-to-package-json
"""

[tasks.build-ws-java-data1-module]
Expand Down
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ members = [
"services/ws-wasm-agent",
"utilities/cli",
"utilities/onnx",
"utilities/pyproject-to-package-json",
"utilities/module-manifest-to-package-json",
]
resolver = "2"

Expand Down
4 changes: 4 additions & 0 deletions services/ws-modules/face-detection/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ repository.workspace = true
[lib]
crate-type = ["cdylib", "rlib"]

[package.metadata.ws-module.dependencies]
et-model-face1 = "*"
onnxruntime-web = "*"

[dependencies]
et-web.workspace = true
et-ws-wasm-agent = { path = "../../ws-wasm-agent" }
Expand Down
6 changes: 0 additions & 6 deletions services/ws-modules/face-detection/package.json

This file was deleted.

3 changes: 3 additions & 0 deletions services/ws-modules/har1/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ repository.workspace = true
[lib]
crate-type = ["cdylib", "rlib"]

[package.metadata.ws-module.dependencies]
et-model-har-motion1 = "*"

[dependencies]
et-web.workspace = true
et-ws-wasm-agent = { path = "../../ws-wasm-agent" }
Expand Down
5 changes: 0 additions & 5 deletions services/ws-modules/har1/package.json

This file was deleted.

39 changes: 38 additions & 1 deletion utilities/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,29 @@ struct PyprojectWsModule {
dependencies: BTreeMap<String, String>,
}

#[derive(Debug, Default, Deserialize)]
struct CargoPackage {
package: Option<CargoPackageMetadata>,
}

#[derive(Debug, Default, Deserialize)]
struct CargoPackageMetadata {
name: Option<String>,
metadata: Option<CargoMetadata>,
}

#[derive(Debug, Default, Deserialize)]
struct CargoMetadata {
#[serde(rename = "ws-module")]
ws_module: Option<CargoWsModule>,
}

#[derive(Debug, Default, Deserialize)]
struct CargoWsModule {
#[serde(default)]
dependencies: BTreeMap<String, String>,
}

#[derive(Debug, Clone)]
struct ModuleRegistryEntry {
mise_path: String,
Expand Down Expand Up @@ -855,7 +878,8 @@ fn module_package_json(module_path: &Path) -> Option<PackageJson> {
let pkg_package = read_package_json(&module_path.join("pkg/package.json"));
let root_package = read_package_json(&module_path.join("package.json"));
let pyproject = read_pyproject_package(&module_path.join("pyproject.toml"));
if pkg_package.is_none() && root_package.is_none() && pyproject.is_none() {
let cargo_package = read_cargo_package(&module_path.join("Cargo.toml"));
if pkg_package.is_none() && root_package.is_none() && pyproject.is_none() && cargo_package.is_none() {
return None;
}

Expand All @@ -874,6 +898,14 @@ fn module_package_json(module_path: &Path) -> Option<PackageJson> {
package.name = pyproject.project.and_then(|project| project.name);
}
}
if let Some(cargo_package) = cargo_package.and_then(|cargo_package| cargo_package.package) {
if let Some(ws_module) = cargo_package.metadata.and_then(|metadata| metadata.ws_module) {
package.dependencies.extend(ws_module.dependencies);
}
if package.name.is_none() {
package.name = cargo_package.name;
}
}
Some(package)
}

Expand All @@ -887,6 +919,11 @@ fn read_pyproject_package(path: &Path) -> Option<PyprojectPackage> {
toml::from_str(&content).ok()
}

fn read_cargo_package(path: &Path) -> Option<CargoPackage> {
let content = fs::read_to_string(path).ok()?;
toml::from_str(&content).ok()
}

fn resolve_module_paths<F>(
registry: &BTreeMap<String, ModuleRegistryEntry>,
module_names: &[String],
Expand Down
27 changes: 27 additions & 0 deletions utilities/cli/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ onnxruntime-web = "*"
);
}

#[test]
fn module_package_json_reads_cargo_ws_module_dependencies() {
let test_root = tempdir().unwrap();
let module_dir = test_root.path().join("rust-module");
fs::create_dir_all(&module_dir).unwrap();
fs::write(
module_dir.join("Cargo.toml"),
r#"[package]
name = "et-ws-rust-module"
version = "0.1.0"
edition = "2024"

[package.metadata.ws-module.dependencies]
et-model-har-motion1 = "*"
"#,
)
.unwrap();

let package = module_package_json(&module_dir).unwrap();

assert_eq!(package.name.as_deref(), Some("et-ws-rust-module"));
assert_eq!(
package.dependencies.get("et-model-har-motion1").map(String::as_str),
Some("*")
);
}

#[test]
fn regenerate_verification_generates_all_deployment_types() {
let test_root = tempdir().unwrap();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[package]
name = "pyproject-to-package-json"
description = "Generate pkg/package.json from a Python ws-module's pyproject.toml"
name = "module-manifest-to-package-json"
description = "Generate pkg/package.json from ws-module project metadata"
version = "0.1.0"
edition.workspace = true
license.workspace = true
repository.workspace = true

[[bin]]
name = "pyproject-to-package-json"
name = "module-manifest-to-package-json"
path = "src/main.rs"

[dependencies]
Expand Down
143 changes: 143 additions & 0 deletions utilities/module-manifest-to-package-json/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};

use serde::Deserialize;
use serde_json::{Map, Value, json};

#[derive(Deserialize)]
struct Project {
name: String,
version: String,
description: Option<String>,
license: Option<String>,
}

#[derive(Deserialize)]
struct WsModule {
#[serde(rename = "js-main")]
js_main: String,
#[serde(default)]
dependencies: BTreeMap<String, String>,
}

#[derive(Deserialize)]
struct Tool {
#[serde(rename = "ws-module")]
ws_module: WsModule,
}

#[derive(Deserialize)]
struct Pyproject {
project: Project,
tool: Tool,
}

#[derive(Deserialize)]
struct CargoPackage {
package: CargoPackageMetadata,
}

#[derive(Deserialize)]
struct CargoPackageMetadata {
name: String,
metadata: Option<CargoMetadata>,
}

#[derive(Deserialize)]
struct CargoMetadata {
#[serde(rename = "ws-module")]
ws_module: Option<CargoWsModule>,
}

#[derive(Deserialize)]
struct CargoWsModule {
#[serde(default)]
dependencies: BTreeMap<String, String>,
}

fn main() {
let out_path = PathBuf::from("pkg/package.json");
let package_json = if Path::new("pyproject.toml").is_file() {
package_json_from_pyproject()
} else if Path::new("Cargo.toml").is_file() {
package_json_from_cargo(&out_path)
} else {
panic!("Expected pyproject.toml or Cargo.toml in the current directory");
};

fs::create_dir_all(out_path.parent().unwrap()).unwrap();
let mut out = serde_json::to_string_pretty(&package_json).unwrap();
out.push('\n');
fs::write(&out_path, &out).unwrap_or_else(|e| panic!("Failed to write {}: {e}", out_path.display()));

println!("Wrote {}", out_path.display());
}

fn package_json_from_pyproject() -> Value {
let pyproject_path = PathBuf::from("pyproject.toml");
let pyproject: Pyproject = read_toml(&pyproject_path);
let p = &pyproject.project;
let mut pkg = Map::from_iter([
("name".to_string(), json!(p.name)),
("type".to_string(), json!("module")),
("description".to_string(), json!(p.description.as_deref().unwrap_or(""))),
("version".to_string(), json!(p.version)),
("license".to_string(), json!(p.license.as_deref().unwrap_or(""))),
("main".to_string(), json!(pyproject.tool.ws_module.js_main)),
]);
if !pyproject.tool.ws_module.dependencies.is_empty() {
pkg.insert("dependencies".to_string(), json!(pyproject.tool.ws_module.dependencies));
}
Value::Object(pkg)
}

fn package_json_from_cargo(out_path: &Path) -> Value {
let cargo_toml: CargoPackage = read_toml(Path::new("Cargo.toml"));
let mut pkg = read_package_json(out_path).unwrap_or_else(|| {
let mut pkg = Map::new();
pkg.insert("name".to_string(), json!(cargo_toml.package.name));
pkg.insert("type".to_string(), json!("module"));
pkg
});

if !pkg.contains_key("name") {
pkg.insert("name".to_string(), json!(cargo_toml.package.name));
}

let Some(ws_module) = cargo_toml.package.metadata.and_then(|metadata| metadata.ws_module) else {
return Value::Object(pkg);
};

if !ws_module.dependencies.is_empty() {
let dependencies = pkg
.entry("dependencies".to_string())
.or_insert_with(|| Value::Object(Map::new()));
let dependency_map = dependencies
.as_object_mut()
.unwrap_or_else(|| panic!("{} contains a non-object dependencies field", out_path.display()));
for (name, version) in ws_module.dependencies {
dependency_map.insert(name, json!(version));
}
}

Value::Object(pkg)
}

fn read_toml<T>(path: &Path) -> T
where
T: for<'de> Deserialize<'de>,
{
let src = fs::read_to_string(path).unwrap_or_else(|e| panic!("Failed to read {}: {e}", path.display()));
toml::from_str(&src).unwrap_or_else(|e| panic!("Failed to parse {}: {e}", path.display()))
}

fn read_package_json(path: &Path) -> Option<Map<String, Value>> {
let src = fs::read_to_string(path).ok()?;
let Value::Object(pkg) =
serde_json::from_str(&src).unwrap_or_else(|e| panic!("Failed to parse {}: {e}", path.display()))
else {
panic!("{} must contain a JSON object", path.display());
};
Some(pkg)
}
Loading
Loading