crate_seq_core/pipeline/
execute.rs1use std::path::{Path, PathBuf};
4use std::process::Command;
5
6use crate_seq_ledger::{load, save};
7use crate_seq_registry::{backoff_publish, BackoffConfig, CratesIoClient, PublishOutcome};
8
9use crate::auth::require_token;
10use crate::pipeline::source::resolve_source;
11use crate::validate::{run_cargo_check, validate_no_path_deps};
12use crate::Error;
13
14#[derive(Debug)]
16pub enum VersionPublishOutcome {
17 Published,
19 AlreadyPublished,
21 Skipped,
23 Failed(String),
25}
26
27#[derive(Debug)]
29pub struct PublishVersionResult {
30 pub version: semver::Version,
32 pub tag_ref: String,
34 pub outcome: VersionPublishOutcome,
36}
37
38#[derive(Debug)]
40pub struct PublishReport {
41 pub crate_name: String,
43 pub results: Vec<PublishVersionResult>,
45}
46
47fn run_package(manifest_path: &Path) -> Result<Option<String>, Error> {
49 let output = Command::new("cargo")
50 .args(["package", "--allow-dirty", "--manifest-path"])
51 .arg(manifest_path)
52 .output()
53 .map_err(|e| Error::Subprocess(e.to_string()))?;
54
55 if output.status.success() {
56 Ok(None)
57 } else {
58 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
59 Ok(Some(stderr))
60 }
61}
62
63fn persist(ledger_path: &Path, ledger: &crate_seq_ledger::CrateSeqLedger) -> Result<(), Error> {
65 save(ledger_path, ledger)?;
66 Ok(())
67}
68
69fn validate_manifest(manifest_path: &Path) -> Result<(), Error> {
71 validate_no_path_deps(manifest_path)?;
72 run_cargo_check(manifest_path)
73}
74
75fn default_snapshot_store(ledger_path: &Path) -> PathBuf {
77 ledger_path
78 .parent()
79 .unwrap_or_else(|| Path::new("."))
80 .join(".crate-seq-snapshots")
81}
82
83#[allow(clippy::too_many_arguments)]
85fn process_entry(
86 entry: &crate_seq_ledger::LedgerEntry,
87 ledger_path: &Path,
88 ledger: &mut crate_seq_ledger::CrateSeqLedger,
89 repo_path: &Path,
90 snapshot_store: &Path,
91 client: &CratesIoClient,
92 token: Option<&str>,
93 backoff: &BackoffConfig,
94 crate_name: &str,
95 results: &mut Vec<PublishVersionResult>,
96) -> Result<bool, Error> {
97 let tag_ref = entry.ref_.clone();
98 let version = entry.version.clone();
99
100 if client.check_version_exists(crate_name, &version)? {
101 ledger.mark_published(&version)?;
102 persist(ledger_path, ledger)?;
103 results.push(PublishVersionResult {
104 version,
105 tag_ref,
106 outcome: VersionPublishOutcome::AlreadyPublished,
107 });
108 return Ok(true);
109 }
110
111 let checkout = resolve_source(entry, repo_path, snapshot_store)?;
112 let manifest_path = checkout.path().join("Cargo.toml");
113
114 validate_manifest(&manifest_path)?;
115
116 crate_seq_manifest::rewrite_version(&manifest_path, &version)?;
117
118 if let Some(stderr) = run_package(&manifest_path)? {
119 results.push(PublishVersionResult {
120 version,
121 tag_ref,
122 outcome: VersionPublishOutcome::Failed(stderr),
123 });
124 persist(ledger_path, ledger)?;
125 return Ok(false);
126 }
127
128 match backoff_publish(checkout.path(), token, backoff)? {
129 PublishOutcome::Success | PublishOutcome::AlreadyPublished => {
130 ledger.mark_published(&version)?;
131 persist(ledger_path, ledger)?;
132 results.push(PublishVersionResult {
133 version,
134 tag_ref,
135 outcome: VersionPublishOutcome::Published,
136 });
137 Ok(true)
138 }
139 PublishOutcome::RateLimited => {
140 results.push(PublishVersionResult {
141 version,
142 tag_ref,
143 outcome: VersionPublishOutcome::Failed(
144 "rate limited — retries exhausted".to_owned(),
145 ),
146 });
147 persist(ledger_path, ledger)?;
148 Ok(false)
149 }
150 PublishOutcome::Failed(msg) => {
151 results.push(PublishVersionResult {
152 version,
153 tag_ref,
154 outcome: VersionPublishOutcome::Failed(msg),
155 });
156 persist(ledger_path, ledger)?;
157 Ok(false)
158 }
159 }
160}
161
162pub fn publish_execute(
185 ledger_path: &Path,
186 repo_path: &Path,
187 token: Option<&str>,
188 backoff_config: &BackoffConfig,
189 crate_seq_version: &str,
190 snapshot_store: Option<PathBuf>,
191) -> Result<PublishReport, Error> {
192 let mut ledger = load(ledger_path)?;
193 let crate_name = ledger.crate_config.name.clone();
194
195 let resolved_token = require_token(token, &ledger.auth)?;
196 let token_ref = resolved_token.as_deref();
197
198 let store = snapshot_store.unwrap_or_else(|| default_snapshot_store(ledger_path));
199
200 let client = CratesIoClient::new(crate_seq_version)?;
201 let pending: Vec<crate_seq_ledger::LedgerEntry> = ledger
202 .pending_versions()
203 .into_iter()
204 .cloned()
205 .collect();
206
207 let mut results = Vec::with_capacity(pending.len());
208
209 for entry in &pending {
210 let ok = process_entry(
211 entry,
212 ledger_path,
213 &mut ledger,
214 repo_path,
215 &store,
216 &client,
217 token_ref,
218 backoff_config,
219 &crate_name,
220 &mut results,
221 )?;
222 if !ok {
223 break;
224 }
225 }
226
227 Ok(PublishReport { crate_name, results })
228}