use anyhow::Context as _;
use std::path::Path;
const POLY_CONFIG_RELATIVE: &str = "poly.toml";
const STALE_SNIPPET_HOOK_RUN: &str = "alef snippets check --strict --cache off";
pub(crate) fn migrate_poly_toml_drop_snippet_hook(base_dir: &Path) -> anyhow::Result<bool> {
let path = crate::cli::pipeline::generate::write::contained_output_path(base_dir, POLY_CONFIG_RELATIVE.as_ref())?;
let Ok(existing) = std::fs::read_to_string(&path) else {
return Ok(false);
};
let Ok(mut doc) = existing.parse::<toml_edit::DocumentMut>() else {
return Ok(false);
};
let commands = doc
.as_table_mut()
.get_mut("hooks")
.and_then(toml_edit::Item::as_table_mut)
.and_then(|hooks| hooks.get_mut("pre-commit"))
.and_then(toml_edit::Item::as_table_mut)
.and_then(|pre_commit| pre_commit.get_mut("commands"))
.and_then(toml_edit::Item::as_table_mut);
let Some(commands) = commands else {
return Ok(false);
};
let is_stale_snippet_hook = commands
.get("alef-snippets")
.and_then(toml_edit::Item::as_table)
.is_some_and(|hook| {
hook.get("run").and_then(toml_edit::Item::as_str) == Some(STALE_SNIPPET_HOOK_RUN)
&& hook.get("workspace").and_then(toml_edit::Item::as_bool) == Some(true)
});
if !is_stale_snippet_hook {
return Ok(false);
}
commands.remove("alef-snippets");
let parent = path.parent().context("poly.toml path has no parent directory")?;
let mut temporary = tempfile::NamedTempFile::new_in(parent)
.with_context(|| format!("failed to create temporary file in {}", parent.display()))?;
std::io::Write::write_all(&mut temporary, doc.to_string().as_bytes())
.with_context(|| format!("failed to write temporary file for {}", path.display()))?;
temporary
.persist(&path)
.map_err(|error| error.error)
.with_context(|| format!("failed to replace {}", path.display()))?;
tracing::info!(
path = %path.display(),
"repaired pre-existing poly.toml: removed the retracted alef-snippets pre-commit hook"
);
Ok(true)
}
const STALE_UNRUNNABLE_SNAPSHOT_HOOK_RUNS: &[(&str, &[&str])] = &[
(
"rubocop",
&[
"bundle exec rubocop",
"ruby -S bundle exec rubocop",
"BUNDLE_PATH=vendor/bundle ruby -S bundle exec ruby -S rubocop",
],
),
(
"steep",
&[
"bundle exec steep check",
"ruby -S bundle exec steep check",
"BUNDLE_PATH=vendor/bundle ruby -S bundle exec ruby -S steep check",
],
),
("dart-analyze", &["dart analyze"]),
("dart-e2e-analyze", &["dart analyze"]),
];
pub(crate) fn migrate_poly_toml_drop_unrunnable_snapshot_hooks(base_dir: &Path) -> anyhow::Result<bool> {
let path = crate::cli::pipeline::generate::write::contained_output_path(base_dir, POLY_CONFIG_RELATIVE.as_ref())?;
let Ok(existing) = std::fs::read_to_string(&path) else {
return Ok(false);
};
let Ok(mut doc) = existing.parse::<toml_edit::DocumentMut>() else {
return Ok(false);
};
let commands = doc
.as_table_mut()
.get_mut("hooks")
.and_then(toml_edit::Item::as_table_mut)
.and_then(|hooks| hooks.get_mut("pre-commit"))
.and_then(toml_edit::Item::as_table_mut)
.and_then(|pre_commit| pre_commit.get_mut("commands"))
.and_then(toml_edit::Item::as_table_mut);
let Some(commands) = commands else {
return Ok(false);
};
let mut changed = false;
for (name, known_runs) in STALE_UNRUNNABLE_SNAPSHOT_HOOK_RUNS {
let is_stale_hook = commands
.get(name)
.and_then(toml_edit::Item::as_table)
.is_some_and(|hook| {
hook.get("run")
.and_then(toml_edit::Item::as_str)
.is_some_and(|run| known_runs.contains(&run))
&& hook.get("workspace").and_then(toml_edit::Item::as_bool) == Some(true)
});
if is_stale_hook {
commands.remove(name);
changed = true;
}
}
if !changed {
return Ok(false);
}
let parent = path.parent().context("poly.toml path has no parent directory")?;
let mut temporary = tempfile::NamedTempFile::new_in(parent)
.with_context(|| format!("failed to create temporary file in {}", parent.display()))?;
std::io::Write::write_all(&mut temporary, doc.to_string().as_bytes())
.with_context(|| format!("failed to write temporary file for {}", path.display()))?;
temporary
.persist(&path)
.map_err(|error| error.error)
.with_context(|| format!("failed to replace {}", path.display()))?;
tracing::info!(
path = %path.display(),
"repaired pre-existing poly.toml: removed retracted pre-commit hooks that cannot pass \
poly's isolated staged snapshot"
);
Ok(true)
}