diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 5c9cd737eef..7cf07b5503c 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,6 +1,110 @@ use moon_cli::run_cli; +use moon_config::constants; +use std::env; +use std::path::{Path, PathBuf}; +use tokio::process::Command; + +/// Check whether this binary has been installed globally or not. +/// If we encounter an error, simply abort early instead of failing. +async fn is_globally_installed() -> bool { + let exe_path = match env::current_exe() { + Ok(path) => path, + Err(_) => return false, + }; + + // Global installs happen *outside* of moon's toolchain, + // so we simply assume that they have and are using npm + // in their environment. + let output = match Command::new("npm") + .args(["config", "get", "prefix"]) + .output() + .await + { + Ok(out) => out, + Err(_) => return false, + }; + + // If our executable path starts with the global dir, + // then we must have been installed globally! + let global_dir = PathBuf::from( + String::from_utf8(output.stdout.to_vec()) + .unwrap_or_default() + .trim(), + ); + + exe_path.starts_with(global_dir) +} + +fn find_workspace_root(dir: &Path) -> Option { + let findable = dir.join(constants::CONFIG_DIRNAME); + + if findable.exists() { + return Some(dir.to_path_buf()); + } + + match dir.parent() { + Some(parent_dir) => find_workspace_root(parent_dir), + None => None, + } +} + +async fn run_bin(bin_path: &Path, current_dir: &Path) -> Result<(), std::io::Error> { + // Remove the binary path from the current args list + let args = env::args() + .enumerate() + .filter(|(i, arg)| { + if *i == 0 { + !arg.ends_with("moon") + } else { + true + } + }) + .map(|(_, arg)| arg) + .collect::>(); + + // Execute the found moon binary with the current filtered args + Command::new(bin_path) + .args(args) + .current_dir(current_dir) + .spawn()? + .wait() + .await?; + + Ok(()) +} #[tokio::main] async fn main() { - run_cli().await + let mut run = true; + + // Detect if we've been installed globally + if let Ok(current_dir) = env::current_dir() { + if is_globally_installed().await { + // If so, find the workspace root so we can locate the + // locally installed `moon` binary in node modules + if let Some(workspace_root) = find_workspace_root(¤t_dir) { + let moon_bin = workspace_root + .join("node_modules") + .join("@moonrepo") + .join("cli") + .join("moon"); + + // The binary exists! So let's run that one to ensure + // we're running the version pinned in `package.json`, + // instead of this global one! + if moon_bin.exists() { + run = false; + + run_bin(&moon_bin, ¤t_dir) + .await + .expect("Failed to run moon binary"); + } + } + } + } + + // Otherwise just run the CLI + if run { + run_cli().await + } } diff --git a/website/docs/install.mdx b/website/docs/install.mdx index 94dd5567dab..a99d86145ea 100644 --- a/website/docs/install.mdx +++ b/website/docs/install.mdx @@ -184,47 +184,23 @@ pnpm add --dev -W @moonrepo/cli ### Accessing the `moon` binary -We suggest _not_ installing the package globally, as adding an explicit development dependency with -a version range ensures all developers and machines are using the same version of moon. With that -being said, how should you access `moon`? Choose a pattern below that works best for you or your -team! - -1. Update your `PATH` environment variable to include a path to the installed node module. This - would need to be added to every developers shell, and other environments like CI. - -```bash title="~/.zshrc" -export PATH="/path/to/repo/node_modules/@moonrepo/cli:$PATH" -``` - -```shell -moon --help -``` - -2. Symlink the binary to the root of the repository. This would require every developer to run the - binary from the repository root (which we suggest). This can be automated through a `postinstall` - script. +For developers, we suggest installing the `@moonrepo/cli` package globally, so that you can easily +run `moon` commands from _any_ directory, instead of relying on `package.json` scripts. When using +this approach, the global must be installed with npm (not pnpm or yarn)! ```shell -ln -s ./node_modules/@moonrepo/cli/moon ./moon +npm install -g @moonrep/cli ``` -```shell -./moon --help -``` - -3. Add the binary as a `package.json` script. This pattern is the most familier for frontend - developers, but also requires the script to be ran from the repository root. Jump to the next - section for more information. - -```shell -yarn run moon --help -``` +Don't worry though, when using the global binary, we ensure the same version of moon is being +executed that was defined as a dependency in the repo. ### Adding a package script -Another way to utilize the `moon` binary would be through a `package.json` script -- but this comes -with a cost -- which is the overhead of launching Node.js and your chosen package manager to execute -the Rust binary, _instead of_ executing the Rust binary directly. +For other scenarios or environments, like CI, `moon` can be ran with through a `package.json` script +-- but this comes with a cost -- which is the overhead of launching Node.js and your chosen package +manager to execute the Rust binary, _instead of_ executing the Rust binary directly. If this is +problematic, feel free to use the global approach below. ```json title="package.json" {