diff --git a/tools/js-runtimes/src/lib.rs b/tools/js-runtimes/src/lib.rs index 6a5f6bf35..8338d4bab 100644 --- a/tools/js-runtimes/src/lib.rs +++ b/tools/js-runtimes/src/lib.rs @@ -32,6 +32,7 @@ pub const ORDER: [Runtime; 4] = [Runtime::Bun, Runtime::Deno, Runtime::Pnpm, Run pub const LOCKED: [Runtime; 3] = [Runtime::Bun, Runtime::Deno, Runtime::Pnpm]; impl Runtime { + /// The runtime's command-line binary name. pub fn name(self) -> &'static str { match self { Runtime::Bun => "bun", @@ -41,6 +42,7 @@ impl Runtime { } } + /// Looks up a runtime by its binary name, e.g. `"bun"`. pub fn parse(s: &str) -> Option { ORDER.iter().copied().find(|r| r.name() == s) } @@ -67,6 +69,7 @@ impl Runtime { } impl fmt::Display for Runtime { + /// Displays the runtime using its binary name. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.name()) } @@ -131,6 +134,7 @@ pub enum EnvKind { } impl EnvKind { + /// The short label used in output, e.g. `"mise"`. pub fn name(self) -> &'static str { match self { EnvKind::Mise => "mise", @@ -158,6 +162,7 @@ pub struct EnvDef { } impl EnvDef { + /// The most-preferred runtime this definition provisions, if any. pub fn best(&self) -> Option { pick(|r| self.provides.contains(&r)) } @@ -424,6 +429,7 @@ pub enum Verdict { None, } +/// Classifies one environment definition against the preference order. pub fn verdict(def: &EnvDef) -> Verdict { match def.best() { Some(Runtime::Bun) => Verdict::Best, @@ -437,6 +443,7 @@ pub fn verdict(def: &EnvDef) -> Verdict { mod tests { use super::*; + /// The preference order is Bun, Deno, pnpm, npm, and `pick` honours it. #[test] fn order_is_bun_deno_pnpm_npm() { assert_eq!(ORDER.map(Runtime::name), ["bun", "deno", "pnpm", "npm"]); @@ -450,12 +457,14 @@ mod tests { assert_eq!(pick(|_| false), None); } + /// npm has no tracked lock; every `LOCKED` runtime does. #[test] fn npm_keeps_no_lock() { assert_eq!(Runtime::Npm.lockfile(), None); assert!(LOCKED.iter().all(|r| r.lockfile().is_some())); } + /// `canon` classifies documents the same way `ijson-jcs check` does. #[test] fn canon_matches_ijson_jcs_check() { assert_eq!(canon(b"{\"a\":1,\"b\":[true,null]}\n"), Canon::Canonical); @@ -472,6 +481,7 @@ mod tests { assert!(matches!(canon(b"not json"), Canon::Invalid(_))); } + /// `has_test_script` finds a string `scripts.test`, and only that. #[test] fn test_script_lookup() { assert!(has_test_script(br#"{"scripts":{"test":"echo ok"}}"#)); @@ -480,6 +490,7 @@ mod tests { assert!(!has_test_script(br#"{}"#)); } + /// Only the `[tools]` table of a mise.toml is read for runtimes. #[test] fn mise_tools_table_only() { let t = "[settings]\nbun = \"no\"\n[tools]\nzig = \"latest\"\nbun = \"1.4.2\" # js\n\"npm:pnpm\" = \"10\"\n[env]\ndeno = \"x\"\n"; @@ -491,6 +502,7 @@ mod tests { assert!(mise_provides("[tools]\njust = \"latest\"\n").is_empty()); } + /// Provisioning requires a base/copied image or install step, not mere use. #[test] fn container_provisioning_not_mere_use() { assert_eq!( @@ -523,6 +535,7 @@ mod tests { assert!(container_provides("# FROM oven/bun\nFROM alpine\n").is_empty()); } + /// Guix package names are matched ignoring commented-out lines. #[test] fn guix_names_packages_ignoring_comments() { assert_eq!( @@ -535,6 +548,7 @@ mod tests { ); } + /// `verdict` covers the Best, Below, Fallback and None outcomes. #[test] fn verdicts() { let d = |kind, provides: Vec| EnvDef { diff --git a/tools/js-runtimes/src/main.rs b/tools/js-runtimes/src/main.rs index 5832bab32..e551a0884 100644 --- a/tools/js-runtimes/src/main.rs +++ b/tools/js-runtimes/src/main.rs @@ -33,16 +33,20 @@ const USAGE: &str = "usage: js-runtimes regen [DIR] | check bun|deno|pnpm [DIR] struct Fail(u8, String); +/// An exit-code-1 failure: a finding was reported. fn finding(msg: impl Into) -> Fail { Fail(1, msg.into()) } +/// An exit-code-2 failure: the check could not run at all. fn cannot(msg: impl Into) -> Fail { Fail(2, msg.into()) } +/// Prints an informational line prefixed with the tool's name. fn note(msg: &str) { println!("js-runtimes: {msg}"); } +/// Whether `bin` is on `PATH` and executable. fn on_path(bin: &str) -> bool { let Some(paths) = env::var_os("PATH") else { return false; @@ -53,16 +57,19 @@ fn on_path(bin: &str) -> bool { }) } +/// Whether the Unix permission bits mark this file executable. #[cfg(unix)] fn is_executable(m: &fs::Metadata) -> bool { use std::os::unix::fs::PermissionsExt; m.permissions().mode() & 0o111 != 0 } +/// Non-Unix platforms have no executable bit to check. #[cfg(not(unix))] fn is_executable(_: &fs::Metadata) -> bool { true } +/// Requires `bin` to be on `PATH`, else a cannot-run failure. fn need(bin: &str) -> Result<(), Fail> { if on_path(bin) { Ok(()) @@ -88,6 +95,7 @@ fn quiet(dir: &Path, argv: &[&str]) -> Result { Ok(out.status.success()) } +/// Run with inherited stdout/stderr, e.g. for the `test` script. fn loud(dir: &Path, argv: &[&str]) -> Result { let st = Command::new(argv[0]) .args(&argv[1..]) @@ -98,6 +106,7 @@ fn loud(dir: &Path, argv: &[&str]) -> Result { Ok(st.success()) } +/// Reads `package.json` from `dir`, or a cannot-run failure. fn manifest(dir: &Path) -> Result<(PathBuf, Vec), Fail> { let p = dir.join("package.json"); match fs::read(&p) { @@ -106,6 +115,7 @@ fn manifest(dir: &Path) -> Result<(PathBuf, Vec), Fail> { } } +/// Rewrites `path` to its canonical JCS form if it is not already. fn canonicalise(path: &Path, bytes: &[u8]) -> Result<(), Fail> { match canon(bytes) { Canon::Canonical => Ok(()), @@ -115,6 +125,8 @@ fn canonicalise(path: &Path, bytes: &[u8]) -> Result<(), Fail> { } } +/// The only writer: canonicalises `package.json`, then regenerates each +/// runtime's lock. fn regen(dir: &Path) -> Result<(), Fail> { let (path, bytes) = manifest(dir)?; for r in LOCKED { @@ -148,6 +160,8 @@ fn regen(dir: &Path) -> Result<(), Fail> { Ok(()) } +/// The gate for one runtime: JCS, lock present, frozen install, then the +/// `test` script. fn check(rt: Runtime, dir: &Path) -> Result<(), Fail> { let Some(lock) = rt.lockfile() else { return Err(cannot(format!( @@ -192,6 +206,7 @@ fn check(rt: Runtime, dir: &Path) -> Result<(), Fail> { Ok(()) } +/// The most-preferred runtime on `PATH`, or a cannot-run failure. fn picked() -> Result { pick(|r| on_path(r.name())).ok_or_else(|| { cannot(format!( @@ -201,6 +216,8 @@ fn picked() -> Result { }) } +/// Checks that every environment definition under `dir` provisions Bun, or +/// the best runtime it can. fn provision(dir: &Path) -> Result<(), Fail> { if !dir.join("package.json").is_file() { note("no package.json: nothing here needs a JavaScript runtime (0 environment definitions examined)"); @@ -277,6 +294,7 @@ fn provision(dir: &Path) -> Result<(), Fail> { Ok(()) } +/// Dispatches the subcommand named by `args[0]`. fn real_main(args: &[String]) -> Result<(), Fail> { let dir_at = |i: usize| PathBuf::from(args.get(i).map(String::as_str).unwrap_or(".")); match args.first().map(String::as_str) { @@ -321,6 +339,7 @@ fn real_main(args: &[String]) -> Result<(), Fail> { } } +/// Entry point: runs `real_main` and maps the result to an exit code. fn main() -> ExitCode { let args: Vec = env::args().skip(1).collect(); match real_main(&args) {