vertigo-cli 0.11.4

Reactive Real-DOM library with SSR for Rust - packaging/serving tool
Documentation
use std::path::PathBuf;

use crate::commons::{ErrorCode, models::IndexModel};

use super::{
    build_opts::BuildOpts,
    cargo_build::run_cargo_build,
    cargo_workspace::{Workspace, get_workspace},
    check_env::check_env,
    find_target::{find_package_rlib_in_target, find_wasm_in_target, profile_name},
    wasm_opt::run_wasm_opt,
    wasm_path::WasmPath,
};

pub fn run(opts: BuildOpts) -> Result<(), ErrorCode> {
    let ws = match get_workspace() {
        Ok(ws) => ws,
        Err(err) => {
            log::error!("Can't read workspace");
            return Err(err);
        }
    };

    run_with_ws(opts, &ws, false)
}

pub fn run_with_ws(opts: BuildOpts, ws: &Workspace, allow_error: bool) -> Result<(), ErrorCode> {
    let package_name = match opts.inner.package_name.as_deref() {
        Some(name) => name.to_string(),
        None => match ws.infer_package_name() {
            Some(name) => {
                log::info!("Inferred package name = {name}");
                name
            }
            None => {
                log::error!(
                    "Can't find vertigo project in {} (no cdylib member)",
                    ws.get_root_dir()
                );
                return Err(ErrorCode::CantFindCdylibMember);
            }
        },
    };

    check_env()?;

    let release = opts.inner.release_mode.unwrap_or(true);
    let profile = profile_name(release);

    let dest_dir = WasmPath::new(PathBuf::from(&opts.common.dest_dir));

    // Clean destination

    dest_dir.remove_dir_all();
    dest_dir.create_dir_all();

    // Delete rlibs to re-generate static files

    find_package_rlib_in_target(&package_name, profile).remove_file()?;

    // Run build

    let target_path = match run_cargo_build(
        &package_name,
        &opts.get_public_path(),
        ws,
        allow_error,
        release,
        &opts.inner.cargo_opts,
    )? {
        Ok(path) => path,
        Err(_) => return Err(ErrorCode::BuildFailed),
    };

    // Get wasm_run.js and index.template.html from vertigo build

    let vertigo_statics_dir = target_path.join("static");

    let mut run_script_content = match std::fs::read(vertigo_statics_dir.join("wasm_run.js")) {
        Ok(content) => content,
        Err(err) => {
            log::error!("Can't read wasm_run from statics directory: {err}");
            return Err(ErrorCode::CantReadWasmRunFromStatics);
        }
    };

    if !opts.inner.wasm_run_source_map {
        erase_last_two_lines(&mut run_script_content);
    }

    let run_script_hash_name = opts
        .new_path_in_static_make(&["wasm_run.js"])
        .save_with_hash(&run_script_content)?;

    if opts.inner.wasm_run_source_map {
        let run_script_sourcemap_content =
            match std::fs::read_to_string(vertigo_statics_dir.join("wasm_run.js.map")) {
                Ok(content) => {
                    // Replace original script filename in sourcemap with the hashed one
                    content.replace("wasm_run.js", &run_script_hash_name)
                }
                Err(err) => {
                    log::error!("Can't read wasm_run sourcemap from statics directory: {err}");
                    return Err(ErrorCode::CantReadWasmRunFromStatics);
                }
            };

        opts.new_path_in_static_make(&["wasm_run.js.map"])
            .save(&run_script_sourcemap_content.into_bytes())?;
    }

    // Copy .wasm to destination

    let wasm_path_target = find_wasm_in_target(&package_name, profile);
    let wasm_path = opts.new_path_in_static_from(&wasm_path_target);

    // Optimize .wasm

    let wasm_path_hash =
        if opts.inner.wasm_opt.unwrap_or(true) && run_wasm_opt(&wasm_path_target, &wasm_path) {
            // optimized
            let wasm_path_hash = wasm_path.save_with_hash(wasm_path.read()?.as_slice())?;
            wasm_path.remove_file()?;
            wasm_path_hash
        } else {
            // copy without optimization
            let wasm_content = wasm_path_target.read()?;
            wasm_path.save_with_hash(wasm_content.as_slice())?
        };

    // Generate index.json in destination

    let index = IndexModel {
        run_js: opts.public_path_to(run_script_hash_name),
        wasm: opts.public_path_to(wasm_path_hash),
    };

    let index_content = serde_json::to_string_pretty(&index).map_err(|err| {
        log::error!("Can't serialize index.json: {err}");
        ErrorCode::CantWriteOrRemoveFile
    })?;
    opts.new_path_in_static_make(&["index.json"])
        .save(index_content.as_bytes())?;

    // Copy statics generated by dom macro invocations

    if let Ok(dir) = std::fs::read_dir(vertigo_statics_dir.join("included")) {
        for entry in dir {
            match entry {
                Ok(entry) => {
                    let src_file_path = WasmPath::new(entry.path());
                    let content = src_file_path.read()?;
                    let dest_file_path = opts.new_path_in_static_from(&src_file_path);
                    dest_file_path.save(&content)?;
                }
                Err(err) => {
                    log::warn!("Can't read entry: {err}");
                    return Err(ErrorCode::CantReadStaticFile);
                }
            }
        }
    }

    Ok(())
}

fn erase_last_two_lines(content: &mut Vec<u8>) {
    // Find the positions of the last two newline characters
    let mut last_newline_pos = None;
    let mut second_last_newline_pos = None;

    for (i, &byte) in content.iter().enumerate() {
        if byte == b'\n' {
            second_last_newline_pos = last_newline_pos; // Update second last
            last_newline_pos = Some(i); // Update last
        }
    }

    // Truncate the Vec<u8> to the position of the second last newline
    if let Some(second_last) = second_last_newline_pos {
        content.truncate(second_last);
    }
}