Skip to main content

docusaurus/
runner.rs

1use std::path::PathBuf;
2use std::process::Command;
3
4use crate::bridge::{find_addon, find_node, write_temp_config};
5use crate::compile::{compile_config, load_config};
6use crate::error::DocusaurusError;
7
8pub struct RunnerOptions {
9    pub site_dir: PathBuf,
10    pub cli_options: serde_json::Value,
11}
12
13/// Compile `docusaurus.config.rs`, serialize the resulting config to JSON, write a
14/// temporary JS shim, then invoke the napi-rs addon via `node` as a subprocess.
15///
16/// `command` must match a named export of the `docusau-rs` addon (e.g. `"build"`, `"start"`).
17pub fn run_command(command: &str, opts: RunnerOptions) -> Result<(), DocusaurusError> {
18    let dylib = compile_config(&opts.site_dir)?;
19    let config = load_config(&dylib)?;
20    let config_json = serde_json::to_string(&config)?;
21
22    // Keep `_temp_file` alive until the node process finishes so the temp file exists.
23    let (_temp_file, config_path) = write_temp_config(&config_json)?;
24
25    let node = find_node()?;
26    let addon = find_addon(&opts.site_dir)?;
27
28    let site_dir_str = opts.site_dir.display().to_string();
29    let config_path_str = config_path.display().to_string();
30    let addon_path_str = addon.display().to_string();
31    // cli_options_json is passed as a JS string literal — the napi fn receives String,
32    // not an object. Escape backslashes and single-quotes for safe embedding.
33    let cli_options_json = opts.cli_options.to_string();
34    let escaped_opts = cli_options_json
35        .replace('\\', r"\\")
36        .replace('\'', r"\'");
37
38    // Inline JS that loads the napi-rs addon and calls the requested command.
39    // This is not execSync — it runs inside the same Node.js process via require().
40    let script = format!(
41        "require('{addon_path_str}').{command}('{site_dir_str}', '{config_path_str}', '{escaped_opts}');"
42    );
43
44    let status = Command::new(node).args(["-e", &script]).status()?;
45
46    if !status.success() {
47        return Err(DocusaurusError::CommandFailed(status.code().unwrap_or(-1)));
48    }
49
50    Ok(())
51}