1#[cfg(test)]
4#[path = "init_tests.rs"]
5mod tests;
6
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10use crate_seq_ledger::{
11 CrateConfig, CrateSeqLedger, LedgerAuth, LedgerEntry, LedgerSettings, LedgerStatus,
12 VersionSource,
13};
14use crate_seq_registry::CratesIoClient;
15
16use crate::{discover_members, Error, WorkspaceMember};
17
18pub struct InitConfig {
20 pub repo_path: PathBuf,
22 pub workspace_root: PathBuf,
24 pub crate_filter: Option<String>,
26 pub crate_seq_version: String,
28}
29
30#[derive(Debug)]
32pub struct InitResult {
33 pub crate_name: String,
35 pub ledger_path: PathBuf,
37 pub version_count: usize,
39}
40
41fn select_members(
43 all: Vec<WorkspaceMember>,
44 filter: Option<&str>,
45) -> Result<Vec<WorkspaceMember>, Error> {
46 match filter {
47 None => Ok(all),
48 Some(name) => {
49 let found = all.into_iter().find(|m| m.name == name);
50 found
51 .map(|m| vec![m])
52 .ok_or_else(|| Error::CrateNotFound(name.to_owned()))
53 }
54 }
55}
56
57fn resolve_tag_pattern(member: &WorkspaceMember, is_workspace: bool) -> String {
59 let ledger_path = member.crate_dir.join(".crate-seq.toml");
60 if ledger_path.exists() {
61 if let Ok(existing) = crate_seq_ledger::load(&ledger_path) {
62 if let Some(pattern) = existing.settings.tag_pattern {
63 return pattern;
64 }
65 }
66 }
67 crate_seq_ledger::detect_tag_pattern(&member.name, is_workspace)
68}
69
70fn tag_name_map(tags: &[crate_seq_git::TagRef]) -> HashMap<semver::Version, String> {
72 tags.iter()
73 .map(|t| (t.semver.clone(), t.name.clone()))
74 .collect()
75}
76
77fn fallback_tag_ref(crate_name: &str, version: &semver::Version) -> String {
79 format!("{crate_name}-v{version}")
80}
81
82fn build_entries(
84 member: &WorkspaceMember,
85 tags: &[crate_seq_git::TagRef],
86 registry: Option<&crate_seq_registry::CrateMetadata>,
87) -> Vec<LedgerEntry> {
88 let tag_map = tag_name_map(tags);
89 let mut entries: Vec<LedgerEntry> = Vec::new();
90
91 if let Some(metadata) = registry {
92 for v in &metadata.versions {
93 let status = if v.yanked {
94 LedgerStatus::Yanked
95 } else {
96 LedgerStatus::Published
97 };
98 let ref_ = tag_map
99 .get(&v.version)
100 .cloned()
101 .unwrap_or_else(|| fallback_tag_ref(&member.name, &v.version));
102 entries.push(LedgerEntry {
103 version: v.version.clone(),
104 source: VersionSource::GitTag,
105 ref_,
106 status,
107 });
108 }
109 }
110
111 let registry_versions: std::collections::HashSet<semver::Version> = registry
112 .map(|m| m.versions.iter().map(|v| v.version.clone()).collect())
113 .unwrap_or_default();
114
115 for tag in tags {
116 if !registry_versions.contains(&tag.semver) {
117 entries.push(LedgerEntry {
118 version: tag.semver.clone(),
119 source: VersionSource::GitTag,
120 ref_: tag.name.clone(),
121 status: LedgerStatus::Pending,
122 });
123 }
124 }
125
126 entries.sort_by(|a, b| a.version.cmp(&b.version));
127 entries
128}
129
130fn init_member(
132 member: &WorkspaceMember,
133 is_workspace: bool,
134 config: &InitConfig,
135 client: &CratesIoClient,
136) -> Result<InitResult, Error> {
137 let tag_pattern = resolve_tag_pattern(member, is_workspace);
138 let tags = crate_seq_git::discover_tags(&config.repo_path, &tag_pattern)?;
139 let registry = client.fetch_crate_metadata(&member.name)?;
140 let entries = build_entries(member, &tags, registry.as_ref());
141 let version_count = entries.len();
142
143 let ledger = CrateSeqLedger {
144 crate_config: CrateConfig {
145 name: member.name.clone(),
146 registry: None,
147 },
148 settings: LedgerSettings {
149 tag_pattern: Some(tag_pattern),
150 ..Default::default()
151 },
152 auth: LedgerAuth::default(),
153 entries,
154 };
155
156 let ledger_path = member.crate_dir.join(".crate-seq.toml");
157 crate_seq_ledger::save(&ledger_path, &ledger)?;
158
159 Ok(InitResult {
160 crate_name: member.name.clone(),
161 ledger_path,
162 version_count,
163 })
164}
165
166pub fn init(config: &InitConfig) -> Result<Vec<InitResult>, Error> {
178 let client = CratesIoClient::new(&config.crate_seq_version)?;
179 init_with_client(config, &client)
180}
181
182pub(crate) fn init_with_client(
187 config: &InitConfig,
188 client: &CratesIoClient,
189) -> Result<Vec<InitResult>, Error> {
190 let all_members = discover_members(&config.workspace_root)?;
191 let members = select_members(all_members, config.crate_filter.as_deref())?;
192 let is_workspace = members.len() > 1;
193
194 let mut results = Vec::with_capacity(members.len());
195 for member in &members {
196 results.push(init_member(member, is_workspace, config, client)?);
197 }
198
199 Ok(results)
200}