diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs index e479b1a77..fb46cbb0c 100644 --- a/crates/cli/src/commands/mod.rs +++ b/crates/cli/src/commands/mod.rs @@ -340,7 +340,12 @@ async fn run_command(_use_tracing: bool) -> Result<()> { // Create and save installation ID if needed let mut updater = Updater::from_current_bin().await?; - updater.dump().await?; + if let Err(e) = updater.dump().await { + log::error!( + "Failed to save manifest, auto-updates will be disabled: {}", + e + ); + } let mut analytics_child = match maybe_spawn_analytics_worker(&app.command, &analytics_args, &updater) { diff --git a/crates/cli/src/updater.rs b/crates/cli/src/updater.rs index 8bb2eebf7..b29a5704e 100644 --- a/crates/cli/src/updater.rs +++ b/crates/cli/src/updater.rs @@ -190,8 +190,7 @@ impl Updater { } let bin_path = install_path.join("bin"); - // Make sure it exists - async_fs::create_dir_all(&bin_path).await?; + let global_grit_path = install_path.join(REPO_CONFIG_DIR_NAME); let updater = Self { manifest_path: install_path.join(MANIFEST_FILE), @@ -324,8 +323,20 @@ impl Updater { app_name: app.get_base_name(), }); updater.configure_version_specifier(axoupdater::UpdateRequest::LatestMaybePrerelease); + + let our_bin = &self.install_path.join("bin"); + + // Make sure it exists + if let Err(e) = async_fs::create_dir_all(&our_bin).await { + return Err(anyhow::anyhow!( + "Failed to prepare install dir at {}: {}", + &our_bin.display(), + e + )); + } + // add bin/ since axoupdater wants to know where bins go - updater.set_install_dir(&self.install_path.join("bin").to_string_lossy()); + updater.set_install_dir(&our_bin.to_string_lossy()); match updater.run().await { Ok(result) => { if let Some(outcome) = result { @@ -375,7 +386,13 @@ impl Updater { /// Dump the manifest to the manifest file pub async fn dump(&self) -> Result<()> { - let mut manifest_file = File::create(&self.manifest_path).await?; + let mut manifest_file = + File::create(&self.manifest_path) + .await + .context(anyhow::anyhow!( + "Failed to create manifest file at {}", + self.manifest_path.display() + ))?; let manifest = Manifest { binaries: self.binaries.clone(), #[cfg(feature = "updater")] diff --git a/crates/cli_bin/fixtures/ro_file/simple.js b/crates/cli_bin/fixtures/ro_file/simple.js new file mode 100644 index 000000000..0e51dcae3 --- /dev/null +++ b/crates/cli_bin/fixtures/ro_file/simple.js @@ -0,0 +1,4 @@ +// This will be deleted +console.log('sanity'); + +// This should be an empty file now diff --git a/crates/cli_bin/tests/filesystem.rs b/crates/cli_bin/tests/filesystem.rs new file mode 100644 index 000000000..521411c54 --- /dev/null +++ b/crates/cli_bin/tests/filesystem.rs @@ -0,0 +1,161 @@ +use std::fs; + +use crate::common::get_fixture; +use anyhow::Result; +use assert_cmd::{cargo::cargo_bin, Command}; +use insta::assert_snapshot; + +mod common; + +fn prepare_read_only_install() -> Result<(tempfile::TempDir, std::path::PathBuf)> { + let bin_path = cargo_bin(env!("CARGO_PKG_NAME")); + let temp_dir = tempfile::tempdir()?; + + let install_dir = temp_dir.path().join("install").join("bin"); + fs::create_dir_all(&install_dir)?; + + let dest_path = install_dir.join("grit"); + + fs::copy(bin_path, &dest_path)?; + + // Make the temp dir read-only + let mut stack = vec![temp_dir.path().to_path_buf()]; + + while let Some(current_dir) = stack.pop() { + for entry in fs::read_dir(¤t_dir)? { + let entry = entry?; + if entry.path().is_dir() { + stack.push(entry.path()); + } + } + let mut perms = fs::metadata(¤t_dir)?.permissions(); + perms.set_readonly(true); + fs::set_permissions(¤t_dir, perms)?; + } + + Ok((temp_dir, dest_path)) +} + +#[test] +fn runs_doctor_from_read_only_dir() -> Result<()> { + let (_temp_dir, dest_path) = prepare_read_only_install()?; + + let mut cmd = Command::new(dest_path); + cmd.arg("doctor"); + + let output = cmd.output()?; + + let stderr = String::from_utf8_lossy(&output.stderr); + println!("stderr: {}", stderr); + + let stdout = String::from_utf8_lossy(&output.stdout); + println!("stdout: {}", stdout); + + assert!( + output.status.success(), + "Command didn't finish successfully" + ); + + Ok(()) +} + +#[test] +fn fails_stdlib_pattern_without_grit_config() -> Result<()> { + let (_temp_dir, fixture_dir) = get_fixture("ro_file", false)?; + + let (_install_dir, bin_path) = prepare_read_only_install()?; + + let mut cmd = Command::new(bin_path); + cmd.current_dir(fixture_dir.clone()); + cmd.arg("apply") + .arg("no_console_log") + .arg("simple.js") + .arg("--force"); + + let output = cmd.output()?; + + println!("output: {}END###", String::from_utf8_lossy(&output.stdout)); + println!("error: {}END###", String::from_utf8_lossy(&output.stderr)); + + assert!(!output.status.success(), "Command was expected to fail"); + + // Make sure output includes the dir, since if we don't have that and we can't initialize it we should fail + assert!(String::from_utf8_lossy(&output.stderr) + .contains(&_install_dir.path().display().to_string()),); + + Ok(()) +} + +#[test] +fn run_stdlib_pattern_with_local_grit_config() -> Result<()> { + let (_temp_dir, fixture_dir) = get_fixture("ro_file", false)?; + + let (_install_dir, bin_path) = prepare_read_only_install()?; + + // Run git init, to make it a repo + let mut cmd = Command::new("git"); + cmd.current_dir(fixture_dir.clone()); + cmd.arg("init"); + + let output = cmd.output()?; + + println!( + "git init output: {}END###", + String::from_utf8_lossy(&output.stdout) + ); + println!( + "git init error: {}END###", + String::from_utf8_lossy(&output.stderr) + ); + + assert!( + output.status.success(), + "Command didn't finish successfully" + ); + + // Run init first + let mut cmd = Command::new(bin_path.clone()); + cmd.current_dir(fixture_dir.clone()); + cmd.arg("init"); + + let output = cmd.output()?; + + println!( + "init output: {}END###", + String::from_utf8_lossy(&output.stdout) + ); + println!( + "init error: {}END###", + String::from_utf8_lossy(&output.stderr) + ); + + assert!( + output.status.success(), + "Command didn't finish successfully" + ); + + let mut cmd = Command::new(bin_path); + cmd.current_dir(fixture_dir.clone()); + cmd.arg("apply") + .arg("no_console_log") + .arg("simple.js") + .arg("--force"); + + let output = cmd.output()?; + + println!("output: {}END###", String::from_utf8_lossy(&output.stdout)); + println!("error: {}END###", String::from_utf8_lossy(&output.stderr)); + + assert!( + output.status.success(), + "Command didn't finish successfully" + ); + + // Read back the require.js file + let content: String = fs_err::read_to_string(fixture_dir.join("simple.js"))?; + + // assert that it matches snapshot + assert_snapshot!(content); + + Ok(()) +} diff --git a/crates/cli_bin/tests/snapshots/filesystem__run_stdlib_pattern_with_local_grit_config.snap b/crates/cli_bin/tests/snapshots/filesystem__run_stdlib_pattern_with_local_grit_config.snap new file mode 100644 index 000000000..1d590994a --- /dev/null +++ b/crates/cli_bin/tests/snapshots/filesystem__run_stdlib_pattern_with_local_grit_config.snap @@ -0,0 +1,7 @@ +--- +source: crates/cli_bin/tests/filesystem.rs +expression: content +--- +// This will be deleted + +// This should be an empty file now diff --git a/crates/gritmodule/src/installer.rs b/crates/gritmodule/src/installer.rs index 5d34dc5cf..d6b871eb0 100644 --- a/crates/gritmodule/src/installer.rs +++ b/crates/gritmodule/src/installer.rs @@ -22,7 +22,7 @@ pub async fn install_default_stdlib( } Err(err) => { bail!( - "Failed to fetch standard library grit module {}: {}", + "Failed to fetch standard library grit module {}. Try running `grit init` first to create a local .grit directory.\n\nOriginal error:{}", stdlib.full_name, err.to_string() )