crate_seq_core/pipeline/
dry_run.rs1use std::path::{Path, PathBuf};
4use std::process::Command;
5
6use crate_seq_ledger::{detect_tag_pattern, load};
7
8use crate::pipeline::patch::inject_workspace_patches;
9use crate::pipeline::source::resolve_source;
10use crate::validate::{run_cargo_check, validate_no_path_deps};
11use crate::Error;
12
13#[derive(Debug)]
15pub enum DryRunOutcome {
16 Pass,
18 PackageFailed(String),
20 PathDepsFound(Vec<String>),
22 CargoCheckFailed(String),
24}
25
26#[derive(Debug)]
28pub struct DryRunVersionResult {
29 pub version: semver::Version,
31 pub tag_ref: String,
33 pub outcome: DryRunOutcome,
35}
36
37#[derive(Debug)]
39pub struct DryRunReport {
40 pub crate_name: String,
42 pub results: Vec<DryRunVersionResult>,
44}
45
46fn resolve_tag_pattern(settings: &crate_seq_ledger::LedgerSettings, crate_name: &str) -> String {
48 settings
49 .tag_pattern
50 .clone()
51 .unwrap_or_else(|| detect_tag_pattern(crate_name, false))
52}
53
54fn run_cargo_package(manifest_path: &Path) -> Result<DryRunOutcome, Error> {
58 let output = Command::new("cargo")
59 .args(["package", "--allow-dirty", "--manifest-path"])
60 .arg(manifest_path)
61 .output()
62 .map_err(|e| Error::Subprocess(e.to_string()))?;
63
64 if output.status.success() {
65 Ok(DryRunOutcome::Pass)
66 } else {
67 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
68 Ok(DryRunOutcome::PackageFailed(stderr))
69 }
70}
71
72fn run_validations(manifest_path: &Path) -> Result<Option<DryRunOutcome>, Error> {
76 match validate_no_path_deps(manifest_path) {
77 Ok(()) => {}
78 Err(Error::PathDependencies { deps, .. }) => {
79 return Ok(Some(DryRunOutcome::PathDepsFound(deps)));
80 }
81 Err(e) => return Err(e),
82 }
83
84 match run_cargo_check(manifest_path) {
85 Ok(()) => {}
86 Err(Error::CargoCheck { stderr }) => {
87 return Ok(Some(DryRunOutcome::CargoCheckFailed(stderr)));
88 }
89 Err(e) => return Err(e),
90 }
91
92 Ok(None)
93}
94
95fn process_entry(
97 entry: &crate_seq_ledger::LedgerEntry,
98 repo_path: &Path,
99 crate_rel_path: &Path,
100 snapshot_store: &Path,
101) -> Result<DryRunVersionResult, Error> {
102 let tag_ref = entry.ref_.clone();
103 let checkout = resolve_source(entry, repo_path, snapshot_store)?;
104 inject_workspace_patches(checkout.path())?; let manifest_path = checkout.path().join(crate_rel_path).join("Cargo.toml");
106
107 if let Some(failed_outcome) = run_validations(&manifest_path)? {
108 return Ok(DryRunVersionResult {
109 version: entry.version.clone(),
110 tag_ref,
111 outcome: failed_outcome,
112 });
113 }
114
115 crate_seq_manifest::rewrite_version(&manifest_path, &entry.version)?;
116 let outcome = run_cargo_package(&manifest_path)?;
117
118 Ok(DryRunVersionResult {
119 version: entry.version.clone(),
120 tag_ref,
121 outcome,
122 })
123}
124
125fn default_snapshot_store(ledger_path: &Path) -> PathBuf {
127 ledger_path
128 .parent()
129 .unwrap_or_else(|| Path::new("."))
130 .join(".crate-seq-snapshots")
131}
132
133fn crate_rel_path(ledger_path: &Path, repo_path: &Path) -> PathBuf {
138 let crate_dir = ledger_path.parent().unwrap_or(repo_path);
139 crate_dir
140 .strip_prefix(repo_path)
141 .unwrap_or(std::path::Path::new(""))
142 .to_owned()
143}
144
145pub fn publish_dry_run(
158 ledger_path: &Path,
159 repo_path: &Path,
160 snapshot_store: Option<PathBuf>,
161) -> Result<DryRunReport, Error> {
162 let ledger = load(ledger_path)?;
163 let crate_name = ledger.crate_config.name.clone();
164 let _tag_pattern = resolve_tag_pattern(&ledger.settings, &crate_name);
165
166 let store = snapshot_store.unwrap_or_else(|| default_snapshot_store(ledger_path));
167 let rel_path = crate_rel_path(ledger_path, repo_path);
168
169 let pending: Vec<crate_seq_ledger::LedgerEntry> =
170 ledger.pending_versions().into_iter().cloned().collect();
171
172 let mut results = Vec::with_capacity(pending.len());
173 for entry in &pending {
174 results.push(process_entry(entry, repo_path, &rel_path, &store)?);
175 }
176
177 Ok(DryRunReport {
178 crate_name,
179 results,
180 })
181}