Skip to main content

runx_cli/
launcher.rs

1use std::ffi::{OsStr, OsString};
2
3pub const DEFAULT_NPM_PACKAGE: &str = "@runxhq/cli@latest";
4
5#[derive(Debug, Eq, PartialEq)]
6pub enum LauncherAction {
7    Delegate(CommandPlan),
8    PrintHelp,
9    PrintVersion,
10}
11
12#[derive(Debug, Eq, PartialEq)]
13pub struct CommandPlan {
14    pub program: OsString,
15    pub args: Vec<OsString>,
16}
17
18pub fn plan_launcher(
19    args: Vec<OsString>,
20    npm_package: Option<OsString>,
21    js_bin: Option<OsString>,
22) -> LauncherAction {
23    if has_arg(&args, "--shim-version") {
24        return LauncherAction::PrintVersion;
25    }
26
27    if has_arg(&args, "--shim-help") {
28        return LauncherAction::PrintHelp;
29    }
30
31    if let Some(js_bin) = non_empty_os(js_bin) {
32        let mut planned_args = Vec::with_capacity(args.len() + 1);
33        planned_args.push(js_bin);
34        planned_args.extend(args);
35        return LauncherAction::Delegate(CommandPlan {
36            program: node_command().into(),
37            args: planned_args,
38        });
39    }
40
41    let package = non_empty_os(npm_package).unwrap_or_else(|| DEFAULT_NPM_PACKAGE.into());
42    let mut planned_args = vec![
43        "exec".into(),
44        "--yes".into(),
45        "--package".into(),
46        package,
47        "--".into(),
48        "runx".into(),
49    ];
50    planned_args.extend(args);
51
52    LauncherAction::Delegate(CommandPlan {
53        program: npm_command().into(),
54        args: planned_args,
55    })
56}
57
58pub fn shim_help() -> String {
59    format!(
60        "\
61runx Cargo launcher
62
63Usage:
64  runx [runx CLI args]
65  runx --shim-version
66  runx --shim-help
67
68Environment:
69  RUNX_NPM_PACKAGE  npm package spec to execute, defaults to {DEFAULT_NPM_PACKAGE}
70  RUNX_JS_BIN       local JavaScript runx entrypoint to execute with node
71"
72    )
73}
74
75pub fn npm_command() -> &'static str {
76    if cfg!(windows) { "npm.cmd" } else { "npm" }
77}
78
79pub fn node_command() -> &'static str {
80    if cfg!(windows) { "node.exe" } else { "node" }
81}
82
83fn has_arg(args: &[OsString], expected: &str) -> bool {
84    args.iter().any(|arg| arg == OsStr::new(expected))
85}
86
87fn non_empty_os(value: Option<OsString>) -> Option<OsString> {
88    value.filter(|value| !value.is_empty())
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn defaults_to_latest_npm_cli_package() {
97        let action = plan_launcher(vec!["--help".into()], None, None);
98
99        assert_eq!(
100            action,
101            LauncherAction::Delegate(CommandPlan {
102                program: npm_command().into(),
103                args: vec![
104                    "exec".into(),
105                    "--yes".into(),
106                    "--package".into(),
107                    DEFAULT_NPM_PACKAGE.into(),
108                    "--".into(),
109                    "runx".into(),
110                    "--help".into(),
111                ],
112            })
113        );
114    }
115
116    #[test]
117    fn accepts_pinned_npm_package() {
118        let action = plan_launcher(
119            vec!["skill".into(), "sourcey".into()],
120            Some("@runxhq/cli@0.5.22".into()),
121            None,
122        );
123
124        assert_eq!(
125            action,
126            LauncherAction::Delegate(CommandPlan {
127                program: npm_command().into(),
128                args: vec![
129                    "exec".into(),
130                    "--yes".into(),
131                    "--package".into(),
132                    "@runxhq/cli@0.5.22".into(),
133                    "--".into(),
134                    "runx".into(),
135                    "skill".into(),
136                    "sourcey".into(),
137                ],
138            })
139        );
140    }
141
142    #[test]
143    fn local_js_bin_overrides_npm_package() {
144        let action = plan_launcher(
145            vec!["--help".into()],
146            Some("@runxhq/cli@0.5.22".into()),
147            Some("/repo/oss/packages/cli/bin/runx.js".into()),
148        );
149
150        assert_eq!(
151            action,
152            LauncherAction::Delegate(CommandPlan {
153                program: node_command().into(),
154                args: vec!["/repo/oss/packages/cli/bin/runx.js".into(), "--help".into()],
155            })
156        );
157    }
158
159    #[test]
160    fn shim_flags_do_not_delegate() {
161        assert_eq!(
162            plan_launcher(vec!["--shim-version".into()], None, None),
163            LauncherAction::PrintVersion
164        );
165        assert_eq!(
166            plan_launcher(vec!["--shim-help".into()], None, None),
167            LauncherAction::PrintHelp
168        );
169    }
170}