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