use super::validate_versions::VersionCheck;
use crate::core::config::ResolvedCrateConfig;
use crate::core::config::extras::Language;
use crate::core::version::to_rubygems_prerelease;
use std::path::{Path, PathBuf};
pub(super) fn collect(config: &ResolvedCrateConfig, workspace_root: &Path, canonical: &str) -> Vec<VersionCheck> {
let mut checks = Vec::new();
collect_gemspec_checks(config, workspace_root, canonical, &mut checks);
collect_swift_package_check(config, workspace_root, canonical, &mut checks);
checks
}
fn collect_gemspec_checks(
config: &ResolvedCrateConfig,
workspace_root: &Path,
canonical: &str,
checks: &mut Vec<VersionCheck>,
) {
if !config.targets(Language::Ruby) {
return;
}
let ruby_dir = config.package_dir(Language::Ruby);
let gemspecs = glob_in_dir(workspace_root, &ruby_dir, "*.gemspec");
if gemspecs.is_empty() {
tracing::error!(
directory = %ruby_dir,
"Ruby is enabled but no .gemspec was found in the configured package directory"
);
checks.push(VersionCheck {
label: format!("{ruby_dir}/*.gemspec"),
found: None,
matches: false,
blocked_on_publish: None,
});
return;
}
let expected = to_rubygems_prerelease(canonical);
for path in gemspecs {
let label = relative_label(workspace_root, &path);
match std::fs::read_to_string(&path) {
Ok(content) => {
if let Some(found) = read_gemspec_version(&content) {
let matches = found == expected;
checks.push(VersionCheck {
label,
found: Some(found),
matches,
blocked_on_publish: None,
});
}
}
Err(error) => {
tracing::error!(gemspec = %label, reason = %error, "gemspec exists but could not be read");
checks.push(VersionCheck {
label,
found: None,
matches: false,
blocked_on_publish: None,
});
}
}
}
}
fn collect_swift_package_check(
config: &ResolvedCrateConfig,
workspace_root: &Path,
canonical: &str,
checks: &mut Vec<VersionCheck>,
) {
if !config.targets(Language::Swift) || crate::scaffold::scaffold_meta(config).repository.is_none() {
return;
}
let label = "Package.swift".to_string();
let path = workspace_root.join(&label);
if !path.exists() {
tracing::error!("Swift is enabled with a configured repository but Package.swift is missing");
checks.push(VersionCheck {
label,
found: None,
matches: false,
blocked_on_publish: None,
});
return;
}
match std::fs::read_to_string(&path) {
Ok(content) => {
if let Some(found) = read_swift_release_version(&content) {
let matches = found == canonical;
checks.push(VersionCheck {
label,
found: Some(found),
matches,
blocked_on_publish: None,
});
}
}
Err(error) => {
tracing::error!(reason = %error, "Package.swift exists but could not be read");
checks.push(VersionCheck {
label,
found: None,
matches: false,
blocked_on_publish: None,
});
}
}
}
fn read_gemspec_version(content: &str) -> Option<String> {
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("spec.version") && trimmed.contains('=') {
let val = trimmed.split_once('=')?.1.trim();
return Some(val.trim_matches('"').trim_matches('\'').to_string());
}
}
None
}
fn read_swift_release_version(content: &str) -> Option<String> {
const MARKER: &str = "releases/download/v";
let start = content.find(MARKER)? + MARKER.len();
let end = content[start..].find('/')?;
let candidate = &content[start..start + end];
candidate
.starts_with(|character: char| character.is_ascii_digit())
.then(|| candidate.to_string())
}
fn glob_in_dir(workspace_root: &Path, directory: &str, suffix: &str) -> Vec<PathBuf> {
let root = glob::Pattern::escape(&workspace_root.to_string_lossy());
let directory = directory.trim_matches(['/', '\\']);
let pattern = format!("{root}/{directory}/{suffix}");
glob::glob(&pattern).into_iter().flatten().flatten().collect()
}
fn relative_label(workspace_root: &Path, path: &Path) -> String {
path.strip_prefix(workspace_root)
.map(|p| p.display().to_string())
.unwrap_or_else(|_| path.display().to_string())
.replace('\\', "/")
}
#[cfg(test)]
#[path = "ruby_swift_versions/tests.rs"]
mod tests;