Skip to main content

fn0_deploy/
bundle.rs

1use anyhow::{Result, anyhow};
2use std::path::Path;
3
4pub fn read_env_yaml(project_dir: &Path) -> Result<Option<Vec<u8>>> {
5    let p = project_dir.join("env.yaml");
6    match std::fs::read(&p) {
7        Ok(content) => Ok(Some(content)),
8        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
9        Err(e) => Err(anyhow!("Failed to read {}: {}", p.display(), e)),
10    }
11}
12
13pub fn create_raw_bundle_wasm(
14    wasm_path: &Path,
15    env_yaml: Option<&[u8]>,
16    output_path: &Path,
17) -> Result<()> {
18    let file = std::fs::File::create(output_path)
19        .map_err(|e| anyhow!("Failed to create {}: {}", output_path.display(), e))?;
20    let mut builder = tar::Builder::new(file);
21    append_bytes(&mut builder, "manifest.json", br#"{"kind":"wasm"}"#)?;
22    let wasm_bytes = std::fs::read(wasm_path)
23        .map_err(|e| anyhow!("Failed to read {}: {}", wasm_path.display(), e))?;
24    validate_wasm(&wasm_bytes, wasm_path)?;
25    append_bytes(&mut builder, "backend.wasm", &wasm_bytes)?;
26    if let Some(env) = env_yaml {
27        append_bytes(&mut builder, "env.yaml", env)?;
28    }
29    builder.finish()?;
30    Ok(())
31}
32
33pub fn create_raw_bundle_forte(
34    dist_dir: &Path,
35    env_yaml: Option<&[u8]>,
36    output_path: &Path,
37) -> Result<()> {
38    let file = std::fs::File::create(output_path)
39        .map_err(|e| anyhow!("Failed to create {}: {}", output_path.display(), e))?;
40    let mut builder = tar::Builder::new(file);
41    append_bytes(&mut builder, "manifest.json", br#"{"kind":"wasmjs"}"#)?;
42
43    let backend_wasm = dist_dir.join("backend.wasm");
44    let wasm_bytes = std::fs::read(&backend_wasm)
45        .map_err(|e| anyhow!("Failed to read {}: {}", backend_wasm.display(), e))?;
46    validate_wasm(&wasm_bytes, &backend_wasm)?;
47    append_bytes(&mut builder, "backend.wasm", &wasm_bytes)?;
48
49    let server_js = dist_dir.join("server.js");
50    let server_bytes = std::fs::read(&server_js)
51        .map_err(|e| anyhow!("Failed to read {}: {}", server_js.display(), e))?;
52    append_bytes(&mut builder, "entry.js", &server_bytes)?;
53
54    if let Some(env) = env_yaml {
55        append_bytes(&mut builder, "env.yaml", env)?;
56    }
57
58    builder.finish()?;
59    Ok(())
60}
61
62/// Rejects a bundle the platform's runtime would refuse to load.
63///
64/// This is stricter than the wasmtime the fleet currently runs: wasmtime 43
65/// accepts constructs later wasmparser releases reject (notably the `async`
66/// canonical option on non-async function types, which wit-bindgen's
67/// `async: true` emits), so a bundle that loads today can stop loading on a
68/// runtime bump. Validating at upload time keeps that failure at the developer's
69/// terminal instead of surfacing as a 500 after a fleet upgrade.
70fn validate_wasm(wasm_bytes: &[u8], source: &Path) -> Result<()> {
71    let mut validator =
72        wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all());
73    validator.validate_all(wasm_bytes).map_err(|e| {
74        anyhow!(
75            "{} is not a valid WebAssembly component: {e}\n\
76             Rebuild it with a current forte-sdk; older SDKs emitted components \
77             that only load on wasmtime 43.",
78            source.display()
79        )
80    })?;
81    Ok(())
82}
83
84fn append_bytes<W: std::io::Write>(
85    builder: &mut tar::Builder<W>,
86    path: &str,
87    data: &[u8],
88) -> Result<()> {
89    let mut header = tar::Header::new_gnu();
90    header.set_size(data.len() as u64);
91    header.set_mode(0o644);
92    header.set_cksum();
93    builder
94        .append_data(&mut header, path, data)
95        .map_err(|e| anyhow!("tar append failed for {}: {}", path, e))?;
96    Ok(())
97}