npmgen-core 0.2.0

Library that generates the npm publish tree shipping a prebuilt Rust binary.
Documentation
//! Generates the launcher shim bundled in the meta package.
//!
//! The script is project-agnostic: at run time it reads its own `package.json`,
//! computes `<platform>-<arch>`, finds the matching `optionalDependency`,
//! resolves the binary, and execs it. Only the missing-binary policy varies.

/// Renders the launcher JavaScript.
#[derive(Debug)]
pub struct LauncherScript {
    fail_open: bool,
}

impl LauncherScript {
    pub fn new(fail_open: bool) -> Self {
        Self { fail_open }
    }

    /// The launcher source. With `fail_open`, a missing binary exits 0 (for
    /// hooks that must not block); otherwise it reports to stderr and exits 1.
    pub fn render(&self) -> String {
        let on_missing = if self.fail_open {
            "  process.exit(0);"
        } else {
            "  process.stderr.write(`npmgen launcher: ${reason}\\n`);\n  process.exit(1);"
        };
        format!(
            r#"// Generated by npmgen. Runs the platform-specific binary for this package.
import {{ spawnSync }} from "node:child_process";
import {{ createRequire }} from "node:module";
import {{ readFileSync }} from "node:fs";
import {{ fileURLToPath }} from "node:url";
import {{ dirname, join }} from "node:path";

const root = dirname(fileURLToPath(import.meta.url));
const key = `${{process.platform}}-${{process.arch}}`;
const ext = process.platform === "win32" ? ".exe" : "";

function missing(reason) {{
{on_missing}
}}

let pkg;
try {{
  pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
}} catch {{
  missing("cannot read package.json");
}}

const dependency = Object.keys(pkg.optionalDependencies ?? {{}}).find((name) => name.endsWith(`-${{key}}`));
const binaryName = (pkg.name ?? "").split("/").pop();
if (!dependency || !binaryName) {{
  missing(`no package for ${{key}}`);
}}

const require = createRequire(import.meta.url);
let binary;
try {{
  binary = require.resolve(`${{dependency}}/${{binaryName}}${{ext}}`);
}} catch {{
  missing(`binary for ${{key}} is not installed`);
}}

const result = spawnSync(binary, process.argv.slice(2), {{ stdio: "inherit" }});
process.exit(result.status ?? 0);
"#
        )
    }
}

#[cfg(test)]
mod tests {
    use super::LauncherScript;

    #[test]
    fn fail_open_exits_zero_on_missing() {
        let source = LauncherScript::new(true).render();
        assert!(source.contains("process.exit(0);"));
        assert!(!source.contains("process.exit(1);"));
        assert!(source.contains("spawnSync"));
    }

    #[test]
    fn fail_hard_reports_and_exits_nonzero() {
        let source = LauncherScript::new(false).render();
        assert!(source.contains("process.exit(1);"));
        assert!(source.contains("process.stderr.write"));
    }
}