forked from JamieMason/syncpack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjavascript.rs
More file actions
127 lines (120 loc) · 4.06 KB
/
javascript.rs
File metadata and controls
127 lines (120 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use {
crate::{
cli::Cli,
rcfile::{
error::{NodeJsResult, RcfileError},
Rcfile,
},
},
log::debug,
std::{path::Path, process::Command},
};
pub fn from_javascript_path(file_path: &Path) -> Result<Rcfile, RcfileError> {
let escaped_file_path_for_nodejs = file_path.to_string_lossy().replace('\\', "\\\\");
let nodejs_script = format!(
r#"
import('{escaped_file_path_for_nodejs}')
.then(findConfig)
.then((value) => {{
if (isNonEmptyObject(value)) {{
console.log(JSON.stringify({{
_tag: 'Ok',
value: JSON.stringify(value),
source: 'import',
}}));
}} else {{
tryRequire('Config expected at default export');
}}
}})
.catch((err) => {{
tryRequire(err.stack || err.message || 'Unknown error in import()');
}});
function tryRequire(importError) {{
Promise.resolve(null)
.then(() => require('{escaped_file_path_for_nodejs}'))
.then(findConfig)
.then((value) => {{
if (isNonEmptyObject(value)) {{
console.log(JSON.stringify({{
_tag: 'Ok',
value: JSON.stringify(value),
source: 'require',
}}));
}} else {{
console.log(JSON.stringify({{
_tag: 'Err',
importError,
requireError: 'Config expected at module.exports',
}}));
}}
}})
.catch((err) => {{
console.log(JSON.stringify({{
_tag: 'Err',
importError,
requireError: err.stack || err.message || 'Unknown require error'
}}));
}});
}};
function isNonEmptyObject(value) {{
return value && typeof value === 'object' && value.constructor === Object && Object.keys(value).length > 0;
}}
function findConfig(mod) {{
return mod.default && mod.default.default ? mod.default.default : mod.default;
}}
"#
);
// Prefer bunx if a Bun lockfile exists in the same directory as the config
let dir = file_path.parent().unwrap_or_else(|| Path::new("."));
let use_bunx = dir.join("bun.lock").exists() || dir.join("bun.lockb").exists();
let runner = if use_bunx { "bunx" } else { "npx" };
Command::new(runner)
.args(["tsx", "-e", &nodejs_script])
.current_dir(file_path.parent().unwrap_or_else(|| Path::new(".")))
.output()
.map_err(RcfileError::NodeJsExecutionFailed)
.and_then(|output| {
if output.status.success() {
Ok(output.stdout)
} else {
Err(RcfileError::ProcessFailed {
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
})
}
})
.and_then(|stdout| String::from_utf8(stdout).map_err(RcfileError::InvalidUtf8))
.inspect(|json_str| {
debug!("Raw output from {:?}: {}", file_path, json_str.trim());
})
.and_then(|json_str| serde_json::from_str::<NodeJsResult>(&json_str).map_err(RcfileError::JsonParseFailed))
.and_then(|response| match response {
NodeJsResult::Success { value } => serde_json::from_str::<Rcfile>(&value).map_err(RcfileError::InvalidConfig),
NodeJsResult::Error {
import_error,
require_error,
} => Err(RcfileError::JavaScriptImportFailed {
import_error,
require_error,
}),
})
}
pub fn try_from_js_candidates(cli: &Cli) -> Option<Result<Rcfile, RcfileError>> {
let candidates = vec![
".syncpackrc.js",
".syncpackrc.ts",
".syncpackrc.mjs",
".syncpackrc.cjs",
"syncpack.config.js",
"syncpack.config.ts",
"syncpack.config.mjs",
"syncpack.config.cjs",
];
for candidate in candidates {
let config_path = cli.cwd.join(candidate);
if config_path.exists() {
debug!("Found JavaScript/TypeScript config file: {config_path:?}");
return Some(from_javascript_path(&config_path));
}
}
None
}