Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid depending on a writable install directory #537

Merged
merged 10 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
25 changes: 21 additions & 4 deletions crates/cli/src/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")]
Expand Down
4 changes: 4 additions & 0 deletions crates/cli_bin/fixtures/ro_file/simple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This will be deleted
console.log('sanity');

// This should be an empty file now
161 changes: 161 additions & 0 deletions crates/cli_bin/tests/filesystem.rs
Original file line number Diff line number Diff line change
@@ -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(&current_dir)? {
let entry = entry?;
if entry.path().is_dir() {
stack.push(entry.path());
}
}
let mut perms = fs::metadata(&current_dir)?.permissions();
perms.set_readonly(true);
fs::set_permissions(&current_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(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
source: crates/cli_bin/tests/filesystem.rs
expression: content
---
// This will be deleted

// This should be an empty file now
2 changes: 1 addition & 1 deletion crates/gritmodule/src/installer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
Expand Down
Loading