From 9293654e826fd2677d3ea88aa8691031eebdeaea Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Tue, 19 Nov 2024 16:45:54 -0800 Subject: [PATCH] Don't manage pnpm through Depot (#76) * Attempt Windows integration * Add nested gitignore test, ensure tests are --no-incremental, remove vike test * Refer to pnpm.exe * Preinstall pnpm on Windows * pnpm.exe? * Don't install pnpm in depot setup if exists on path * Don't search for .exe * Give up for now * Don't manage pnpm. I give up * Add Node and pnpm to CI * Disable incremental compilation by default. Check for node/pnpm --- .github/workflows/pre-release.yaml | 8 ++ .github/workflows/tests.yaml | 8 ++ README.md | 10 +- crates/depot-test-utils/src/lib.rs | 11 -- crates/depot/src/commands/mod.rs | 3 - crates/depot/src/commands/new.rs | 18 +-- crates/depot/src/commands/setup.rs | 165 --------------------------- crates/depot/src/lib.rs | 35 +++--- crates/depot/src/utils.rs | 11 ++ crates/depot/src/workspace/mod.rs | 25 ++-- crates/depot/src/workspace/runner.rs | 2 +- scripts/install.sh | 4 - 12 files changed, 62 insertions(+), 238 deletions(-) delete mode 100644 crates/depot/src/commands/setup.rs diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index 40dd6e0..49f3379 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -35,6 +35,14 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: 20.15.0 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.13.2 - name: Add target run: rustup target add ${{ matrix.target }} - name: Cross-compile diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f3ed7f4..b7e2b25 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,6 +20,14 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: 20.15.0 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.13.2 - name: Run tests run: cargo test --features dev -- --test-threads=1 - name: Run lints diff --git a/README.md b/README.md index feda757..d0ebbbc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Depot (formerly Graco) is a tool for orchestrating other Javascript devtools. As an analogy: * Depot is like [Cargo], but for Javascript. * Depot is like [create-react-app], but for people who like software engineering. -* Depot is like the [`"scripts"` field of package.json](https://docs.npmjs.com/cli/v9/using-npm/scripts), but with more power and flexibility. +* Depot is like the [`"scripts"` field of package.json][package.json], but with more power and flexibility. Depot works on Javascript workspaces that have been created by Depot, specifically those using the [model JS workspace] format. Depot supports the following commands: @@ -26,7 +26,9 @@ A few benefits of using Depot: ## Installation -The [install script](https://github.com/cognitive-engineering-lab/depot/blob/main/scripts/install.sh) will download a prebuilt binary if possible. Run the script as follows: +As prerequisites, you must have [NodeJS][node-install] (≥20) and [pnpm][pnpm-install] (≥9.9) installed on your computer. + +The [install script] will download a prebuilt binary if possible. Run the script as follows: ``` curl https://raw.githubusercontent.com/cognitive-engineering-lab/depot/main/scripts/install.sh | sh @@ -98,3 +100,7 @@ Depot is used in a few of our projects: [Biome]: https://biomejs.dev/ [Typedoc]: https://typedoc.org/ [pnpm]: https://pnpm.io/ +[node-install]: https://nodejs.org/en/download/package-manager +[pnpm-install]: https://pnpm.io/installation +[install script]: https://github.com/cognitive-engineering-lab/depot/blob/main/scripts/install.sh +[package.json]: https://docs.npmjs.com/cli/v9/using-npm/scripts \ No newline at end of file diff --git a/crates/depot-test-utils/src/lib.rs b/crates/depot-test-utils/src/lib.rs index 77efe46..51ef339 100644 --- a/crates/depot-test-utils/src/lib.rs +++ b/crates/depot-test-utils/src/lib.rs @@ -5,7 +5,6 @@ use std::{ fs, path::{Path, PathBuf}, process::Command, - sync::Once, }; use either::Either; @@ -25,17 +24,8 @@ fn new_cmd(s: impl AsRef) -> String { format!("{} --prefer-offline", s.as_ref()) } -static SETUP: Once = Once::new(); - impl ProjectBuilder { pub fn new() -> Self { - SETUP.call_once(|| { - let status = Command::new(depot_exe()).arg("setup").status().unwrap(); - if !status.success() { - panic!("depot setup failed"); - } - }); - let tmpdir = TempDir::new().unwrap(); ProjectBuilder { tmpdir: Either::Left(tmpdir), @@ -69,7 +59,6 @@ impl ProjectBuilder { dir: impl AsRef, ) -> Result { let mut process = Command::new(depot_exe()); - process.arg("--no-incremental"); process.current_dir(dir); process.args(shlex::split(cmd.as_ref()).unwrap()); diff --git a/crates/depot/src/commands/mod.rs b/crates/depot/src/commands/mod.rs index 30fe521..35c9eaf 100644 --- a/crates/depot/src/commands/mod.rs +++ b/crates/depot/src/commands/mod.rs @@ -5,7 +5,6 @@ pub mod fix; pub mod fmt; pub mod init; pub mod new; -pub mod setup; pub mod test; #[derive(clap::Subcommand)] @@ -30,6 +29,4 @@ pub enum Command { Fix(fix::FixArgs), Init(init::InitArgs), - - Setup(setup::SetupArgs), } diff --git a/crates/depot/src/commands/new.rs b/crates/depot/src/commands/new.rs index ecc7716..b9e5a6e 100644 --- a/crates/depot/src/commands/new.rs +++ b/crates/depot/src/commands/new.rs @@ -23,8 +23,6 @@ use crate::{ CommonArgs, }; -use super::setup::GlobalConfig; - const REACT_INDEX: &str = r#"import React from "react"; import ReactDOM from "react-dom/client"; @@ -103,7 +101,6 @@ pub struct NewArgs { pub struct NewCommand { args: NewArgs, ws_opt: Option, - global_config: GlobalConfig, } fn json_merge(a: &mut Value, b: Value) { @@ -157,15 +154,9 @@ fn test_json_merge() { type FileVec = Vec<(PathBuf, Cow<'static, str>)>; impl NewCommand { - pub async fn new(args: NewArgs, global_config: GlobalConfig) -> Self { - let ws_opt = Workspace::load(global_config.clone(), None, CommonArgs::default()) - .await - .ok(); - Self { - args, - ws_opt, - global_config, - } + pub async fn new(args: NewArgs) -> Self { + let ws_opt = Workspace::load(None, CommonArgs::default()).await.ok(); + Self { args, ws_opt } } fn new_workspace(self, root: &Path) -> Result<()> { @@ -547,7 +538,8 @@ export default defineConfig(({{ mode }}) => ({{ } fn run_pnpm(&self, f: impl Fn(&mut Command)) -> Result<()> { - let mut cmd = Command::new(self.global_config.pnpm_path()); + let pnpm_bin = utils::find_pnpm(None).context("Could not find pnpm")?; + let mut cmd = Command::new(pnpm_bin); f(&mut cmd); if self.args.offline { diff --git a/crates/depot/src/commands/setup.rs b/crates/depot/src/commands/setup.rs deleted file mode 100644 index 80e5446..0000000 --- a/crates/depot/src/commands/setup.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::utils; -use std::{ - env, - fs::File, - io::{BufWriter, Write}, - path::{Path, PathBuf}, -}; -#[cfg(unix)] -use std::{fs::Permissions, os::unix::prelude::PermissionsExt}; - -use anyhow::{anyhow, ensure, Context, Result}; -use futures::StreamExt; -use indicatif::{ProgressBar, ProgressStyle}; - -/// Setup Depot for use on this machine -#[derive(clap::Parser)] -pub struct SetupArgs { - /// Directory for global Depot configuration, defaults to $HOME/.depot - #[arg(short, long)] - pub config_dir: Option, -} - -pub struct SetupCommand { - args: SetupArgs, -} - -#[derive(Clone)] -pub struct GlobalConfig { - root: PathBuf, - pnpm_path: PathBuf, -} - -const HOME_ENV_VAR: &str = "DEPOT_HOME"; - -fn find_pnpm(root: &Path) -> Option { - let pnpm_in_root = root.join("bin").join("pnpm"); - if pnpm_in_root.exists() { - Some(pnpm_in_root) - } else { - pathsearch::find_executable_in_path("pnpm") - } -} - -impl GlobalConfig { - fn find_root() -> Result { - match env::var(HOME_ENV_VAR) { - Ok(val) => Ok(PathBuf::from(val)), - Err(_) => { - let home_dir = home::home_dir().context("Could not find home directory")?; - Ok(home_dir.join(".local")) - } - } - } - - pub fn load() -> Result { - let root = Self::find_root()?; - ensure!( - root.exists(), - "Depot global config directory does not exist: {}", - root.display() - ); - - let pnpm_path = find_pnpm(&root).ok_or(anyhow!("pnpm is not installed"))?; - Ok(GlobalConfig { root, pnpm_path }) - } - - pub fn pnpm_path(&self) -> &Path { - &self.pnpm_path - } -} - -const PNPM_VERSION: &str = "9.9.0"; - -async fn download_file(url: &str, mut dst: impl Write) -> Result<()> { - let res = reqwest::get(url).await?; - let total_size = res - .content_length() - .context("Failed to get content length")?; - - log::debug!("Starting download..."); - let bar = ProgressBar::new(total_size); - bar.set_style( - ProgressStyle::with_template( - "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes}", - ) - .unwrap() - .progress_chars("#>-"), - ); - - let mut stream = res.bytes_stream(); - let mut downloaded: u64 = 0; - while let Some(chunk) = stream.next().await { - let chunk = chunk?; - dst.write_all(&chunk)?; - let new = (downloaded + (chunk.len() as u64)).min(total_size); - downloaded = new; - bar.set_position(new); - } - - bar.finish(); - - Ok(()) -} - -async fn download_pnpm(dst: &Path) -> Result<()> { - let version = PNPM_VERSION; - let platform = match env::consts::OS { - "macos" | "ios" => "macos", - "windows" => "win", - _ => "linuxstatic", - }; - let arch = match env::consts::ARCH { - "arm" | "aarch64" => "arm64", - _ => "x64", - }; - - let pnpm_url = - format!("https://github.com/pnpm/pnpm/releases/download/v{version}/pnpm-{platform}-{arch}"); - - let mut file = File::create(dst).context("Could not save pnpm binary to file")?; - download_file(&pnpm_url, BufWriter::new(&mut file)).await?; - - #[cfg(unix)] - file.set_permissions(Permissions::from_mode(0o555))?; - - Ok(()) -} - -impl SetupCommand { - pub fn new(args: SetupArgs) -> Self { - SetupCommand { args } - } - - pub async fn run(self) -> Result<()> { - let config_dir = match self.args.config_dir { - Some(dir) => dir, - None => GlobalConfig::find_root()?, - }; - utils::create_dir_if_missing(&config_dir)?; - - let config = GlobalConfig { - root: config_dir, - pnpm_path: PathBuf::new(), - }; - let bindir = config.root.join("bin"); - utils::create_dir_if_missing(&bindir)?; - - let pnpm_path = find_pnpm(&config.root); - if pnpm_path.is_none() { - println!("Downloading pnpm from Github..."); - - #[cfg(unix)] - let pnpm_bin = "pnpm"; - #[cfg(not(unix))] - let pnpm_bin = "pnpm.exe"; - - let pnpm_dst = bindir.join(pnpm_bin); - download_pnpm(&pnpm_dst).await?; - } - - println!("Setup complete!"); - - Ok(()) - } -} diff --git a/crates/depot/src/lib.rs b/crates/depot/src/lib.rs index 51d4f92..8d60c6a 100644 --- a/crates/depot/src/lib.rs +++ b/crates/depot/src/lib.rs @@ -8,18 +8,11 @@ )] use self::commands::Command; -use anyhow::{Context, Result}; +use anyhow::{bail, Result}; use clap::Parser; use commands::{ - build::BuildCommand, - clean::CleanCommand, - doc::DocCommand, - fix::FixCommand, - fmt::FmtCommand, - init::InitCommand, - new::NewCommand, - setup::{GlobalConfig, SetupCommand}, - test::TestCommand, + build::BuildCommand, clean::CleanCommand, doc::DocCommand, fix::FixCommand, fmt::FmtCommand, + init::InitCommand, new::NewCommand, test::TestCommand, }; use workspace::{package::PackageName, Workspace}; @@ -34,9 +27,9 @@ pub struct CommonArgs { #[clap(short, long)] package: Option, - /// Disable incremental compilation + /// Enable incremental compilation #[clap(long)] - no_incremental: bool, + incremental: bool, /// Disable fullscreen UI #[clap(long)] @@ -57,20 +50,20 @@ struct Args { pub async fn run() -> Result<()> { let Args { command, common } = Args::parse(); - let command = match command { - Command::Setup(args) => return SetupCommand::new(args).run().await, - command => command, - }; + if utils::find_node().is_none() { + bail!("Failed to find `node` installed on your path. Depot requires NodeJS to be installed. See: https://nodejs.org/en/download/package-manager"); + } - let global_config = - GlobalConfig::load().context("Depot has not been setup yet. Run `depot setup` to proceed.")?; + if utils::find_pnpm(None).is_none() { + bail!("Failed to find `pnpm` installed on your path. Depot requires pnpm to be installed. See: https://pnpm.io/installation") + } let command = match command { - Command::New(args) => return NewCommand::new(args, global_config).await.run(), + Command::New(args) => return NewCommand::new(args).await.run(), command => command, }; - let ws = Workspace::load(global_config, None, common).await?; + let ws = Workspace::load(None, common).await?; // TODO: merge all tasks into a single task graph like Cargo let command = match command { @@ -81,7 +74,7 @@ pub async fn run() -> Result<()> { Command::Clean(args) => CleanCommand::new(args).kind(), Command::Doc(args) => DocCommand::new(args).kind(), Command::Fix(args) => FixCommand::new(args).kind(), - Command::Setup(..) | Command::New(..) => unreachable!(), + Command::New(..) => unreachable!(), }; ws.run(command).await?; diff --git a/crates/depot/src/utils.rs b/crates/depot/src/utils.rs index d7143a9..586f07b 100644 --- a/crates/depot/src/utils.rs +++ b/crates/depot/src/utils.rs @@ -100,3 +100,14 @@ macro_rules! shareable { } }; } + +pub fn find_node() -> Option { + pathsearch::find_executable_in_path("node") +} + +pub fn find_pnpm(root: Option<&Path>) -> Option { + let pnpm_in_root = root + .map(|root| root.join("bin").join("pnpm")) + .filter(|root| root.exists()); + pnpm_in_root.or_else(|| pathsearch::find_executable_in_path("pnpm")) +} diff --git a/crates/depot/src/workspace/mod.rs b/crates/depot/src/workspace/mod.rs index 44c32a1..f1fe56d 100644 --- a/crates/depot/src/workspace/mod.rs +++ b/crates/depot/src/workspace/mod.rs @@ -4,9 +4,9 @@ use self::{ package::{PackageGraph, PackageIndex}, process::Process, }; -use crate::{commands::setup::GlobalConfig, shareable, utils, CommonArgs}; +use crate::{shareable, utils, CommonArgs}; -use anyhow::{ensure, Context, Result}; +use anyhow::{anyhow, ensure, Context, Result}; use futures::{ stream::{self, TryStreamExt}, StreamExt, @@ -55,9 +55,6 @@ pub struct WorkspaceInner { /// True if this workspace is structured as a monorepo with a `packages/` directory. pub monorepo: bool, - /// Depot configuration read from disk. - pub global_config: GlobalConfig, - /// CLI arguments that apply to the whole workspace. pub common: CommonArgs, @@ -201,11 +198,7 @@ pub trait WorkspaceCommand: CoreCommand + Debug + Send + Sync + 'static { pub const DEPOT_VERSION: &str = env!("CARGO_PKG_VERSION"); impl Workspace { - pub async fn load( - global_config: GlobalConfig, - cwd: Option, - common: CommonArgs, - ) -> Result { + pub async fn load(cwd: Option, common: CommonArgs) -> Result { let cwd = match cwd { Some(cwd) => cwd, None => env::current_dir()?, @@ -291,7 +284,6 @@ Double-check that this workspace is compatible and update depot.depot_version in packages, package_display_order, monorepo, - global_config, pkg_graph, common, roots, @@ -324,15 +316,12 @@ impl WorkspaceInner { let ws_bindir = self.root.join("node_modules").join(".bin"); let script_path = if script == "pnpm" { - self.global_config.pnpm_path().to_owned() + utils::find_pnpm(Some(&self.root)).ok_or(anyhow!("could not find pnpm on your system"))? } else { - ws_bindir.join(script) + let path = ws_bindir.join(script); + ensure!(path.exists(), "Executable is missing: {}", path.display()); + path }; - ensure!( - script_path.exists(), - "Executable is missing: {}", - script_path.display() - ); let mut cmd = tokio::process::Command::new(script_path); cmd.current_dir(&self.root); diff --git a/crates/depot/src/workspace/runner.rs b/crates/depot/src/workspace/runner.rs index 1894821..5aeb85f 100644 --- a/crates/depot/src/workspace/runner.rs +++ b/crates/depot/src/workspace/runner.rs @@ -128,7 +128,7 @@ impl Workspace { .borrow_mut() .entry($key.clone()) .or_insert_with(|| { - let can_skip = !self.common.no_incremental + let can_skip = self.common.incremental && !matches!(runtime, Some(CommandRuntime::RunForever)) && match $files { Some(files) => { diff --git a/scripts/install.sh b/scripts/install.sh index 873539d..2c32947 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -44,9 +44,5 @@ pick_target() { } pick_target $1 -echo 'The depot binary is installed. Running `depot setup`...' - -PATH=$PATH:$INSTALL_DIR -depot setup echo 'Depot installation is complete.' \ No newline at end of file