1use std::{env, fs, path::PathBuf, process::Command};
2
3use anyhow::{bail, Context, Result};
4use sha2::{Digest, Sha256};
5use testing::CARGO_TARGET_DIR;
6use tracing::debug;
7
8#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct JsExecOptions {
10 pub cache: bool,
22
23 pub module: bool,
25
26 pub args: Vec<String>,
28}
29
30fn cargo_cache_root() -> PathBuf {
31 env::var("SWC_ECMA_TESTING_CACHE_DIR")
32 .map(PathBuf::from)
33 .unwrap_or_else(|_| CARGO_TARGET_DIR.clone())
34}
35
36pub fn exec_node_js(js_code: &str, opts: JsExecOptions) -> Result<String> {
38 if opts.cache {
39 let hash = calc_hash(&format!("{:?}:{}", opts.args, js_code));
40 let cache_dir = cargo_cache_root().join(".swc-node-exec-cache");
41 let cache_path = cache_dir.join(format!("{hash}.stdout"));
42
43 if let Ok(s) = fs::read_to_string(&cache_path) {
44 return Ok(s);
45 }
46
47 let output = exec_node_js(
48 js_code,
49 JsExecOptions {
50 cache: false,
51 ..opts
52 },
53 )?;
54
55 fs::create_dir_all(&cache_dir).context("failed to create cache directory")?;
56
57 fs::write(&cache_path, output.as_bytes()).context("failed to write cache")?;
58
59 return Ok(output);
60 }
61
62 debug!("Executing nodejs:\n{}", js_code);
63
64 let mut c = Command::new("node");
65
66 if opts.module {
67 c.arg("--input-type=module");
68 } else {
69 c.arg("--input-type=commonjs");
70 }
71
72 c.arg("-e").arg(js_code);
73
74 for arg in opts.args {
75 c.arg(arg);
76 }
77
78 let output = c.output().context("failed to execute output of minifier")?;
79
80 if !output.status.success() {
81 bail!(
82 "failed to execute:\n{}\n{}",
83 String::from_utf8_lossy(&output.stdout),
84 String::from_utf8_lossy(&output.stderr)
85 )
86 }
87
88 String::from_utf8(output.stdout).context("output is not utf8")
89}
90
91fn calc_hash(s: &str) -> String {
92 let mut hasher = Sha256::default();
93 hasher.update(s.as_bytes());
94 let sum = hasher.finalize();
95
96 hex::encode(sum)
97}