use super::*;
use anodizer_core::artifact::{Artifact, ArtifactKind};
use anodizer_core::config::{Config, GitHubConfig};
use anodizer_core::context::Context;
use anodizer_core::git;
use anodizer_core::log::StageLogger;
use anodizer_core::scm::{self, ScmTokenType};
use anyhow::{Context as _, Result};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub fn infer_project_name(config: &mut Config, log: &StageLogger) {
if !config.project_name.is_empty() {
return;
}
if let Ok(cargo_toml) = std::fs::read_to_string("Cargo.toml")
&& let Ok(doc) = cargo_toml.parse::<toml_edit::DocumentMut>()
&& let Some(name) = doc
.get("package")
.and_then(|p| p.get("name"))
.and_then(|n| n.as_str())
{
config.project_name = name.to_string();
log.verbose(&format!("inferred project_name '{}' from Cargo.toml", name));
}
}
pub fn auto_detect_github(config: &mut Config, log: &StageLogger) {
let detected_github = git::resolve_github_slug(None, None).ok();
let crates_iter = config.crates.iter_mut().chain(
config
.workspaces
.iter_mut()
.flatten()
.flat_map(|w| w.crates.iter_mut()),
);
for crate_cfg in crates_iter {
if let Some(ref mut release) = crate_cfg.release
&& release.github.is_none()
{
if let Some(slug) = &detected_github {
release.github = Some(GitHubConfig {
owner: slug.owner().to_string(),
name: slug.name().to_string(),
token: None,
});
} else {
log.warn("could not auto-detect GitHub repo from git remote");
}
}
}
}
pub fn setup_context(ctx: &mut Context, config: &Config, log: &StageLogger) -> Result<()> {
resolve_scm_token_type(ctx, config);
ctx.populate_time_vars();
ctx.populate_runtime_vars();
ctx.template_vars_mut().set_bool("IsPrepare", false);
setup_env(ctx, config, log)?;
resolve_git_context(ctx, config, log)?;
Ok(())
}
pub fn resolve_scm_token_type(ctx: &mut Context, config: &Config) {
let env_hint = if ctx.env_var("GITLAB_TOKEN").is_some() {
Some("gitlab")
} else if ctx.env_var("GITEA_TOKEN").is_some() {
Some("gitea")
} else {
None
};
let provider_force = config.release.as_ref().and_then(|r| r.provider.clone());
let force_token =
provider_force.or_else(|| resolve_force_token_with_env(config, ctx.env_source()));
ctx.token_type = scm::resolve_token_type(force_token.as_ref(), env_hint);
if ctx.options.token.is_none() {
ctx.options.token = match ctx.token_type {
ScmTokenType::GitLab => ctx.env_var("GITLAB_TOKEN"),
ScmTokenType::Gitea => ctx.env_var("GITEA_TOKEN"),
ScmTokenType::GitHub => {
anodizer_core::git::resolve_github_token_with_env(None, &|key| ctx.env_var(key))
}
};
}
}
pub fn init_publish_stage_ctx(
config_override: Option<&Path>,
ctx_opts: anodizer_core::context::ContextOptions,
dist_override: Option<&Path>,
infer_project: bool,
log: &StageLogger,
) -> Result<(Config, Context, std::path::PathBuf)> {
let config_path = crate::pipeline::find_config_with_logger(config_override, Some(log))?;
let mut config = crate::pipeline::load_config(&config_path)?;
if infer_project {
infer_project_name(&mut config, log);
}
auto_detect_github(&mut config, log);
let mut ctx = Context::new(config.clone(), ctx_opts);
crate::pipeline::emit_config_advisories_filtered(&config, log, |name| {
ctx.publisher_deselected(name)
});
setup_context(&mut ctx, &config, log)?;
let dist = dist_override.unwrap_or(&config.dist).to_path_buf();
load_artifacts_from_dist(&mut ctx, &dist)?;
log.status(&format!(
"loaded {} artifact(s) from {}",
ctx.artifacts.all().len(),
dist.display()
));
Ok((config, ctx, dist))
}
pub fn init_merge_stage_ctx(
config_override: Option<&Path>,
ctx_opts: anodizer_core::context::ContextOptions,
log: &StageLogger,
) -> Result<(Config, Context)> {
let config_path = crate::pipeline::find_config_with_logger(config_override, Some(log))?;
let mut config = crate::pipeline::load_config(&config_path)?;
infer_project_name(&mut config, log);
auto_detect_github(&mut config, log);
let mut ctx = Context::new(config.clone(), ctx_opts);
crate::pipeline::emit_config_advisories_filtered(&config, log, |name| {
ctx.publisher_deselected(name)
});
setup_context(&mut ctx, &config, log)?;
ctx.populate_metadata_var()?;
Ok((config, ctx))
}
pub fn load_artifacts_from_dist(ctx: &mut Context, dist: &Path) -> Result<()> {
let artifacts_path = dist.join(anodizer_core::dist::ARTIFACTS_JSON);
load_artifacts_from_manifest(ctx, dist, &artifacts_path)
}
pub fn load_artifacts_from_manifest(
ctx: &mut Context,
dist: &Path,
manifest_path: &Path,
) -> Result<()> {
if !manifest_path.exists() {
anyhow::bail!(
"no artifacts manifest found at {} (under {}). Run a full release or merge first.",
manifest_path.display(),
dist.display()
);
}
let content = std::fs::read_to_string(manifest_path)
.with_context(|| format!("read {}", manifest_path.display()))?;
#[derive(serde::Deserialize)]
struct MetadataArtifact {
kind: String,
#[serde(default)]
name: Option<String>,
path: String,
target: Option<String>,
crate_name: String,
#[serde(default)]
metadata: HashMap<String, String>,
#[serde(default)]
size: Option<u64>,
}
let artifacts: Vec<MetadataArtifact> = serde_json::from_str(&content)
.with_context(|| format!("parse {}", manifest_path.display()))?;
for a in artifacts {
let kind = ArtifactKind::parse(&a.kind)
.ok_or_else(|| anyhow::anyhow!("unknown artifact kind: {}", a.kind))?;
let path_str = a.path.as_str();
let rewritten = if let Some(rel) = path_str
.strip_prefix("./dist/")
.or_else(|| path_str.strip_prefix("dist/"))
{
dist.join(rel)
} else {
std::path::PathBuf::from(path_str)
};
if a.target.is_none()
&& ctx
.artifacts
.all()
.iter()
.any(|existing| existing.target.is_none() && existing.path == rewritten)
{
continue;
}
ctx.artifacts.add(Artifact {
kind,
name: a.name.unwrap_or_default(),
path: rewritten,
target: a.target,
crate_name: a.crate_name,
metadata: a.metadata,
size: a.size,
});
}
Ok(())
}
pub(crate) fn discover_workspace_root(config_override: Option<&Path>) -> Result<PathBuf> {
let cwd = std::env::current_dir().context("failed to read current directory")?;
let absolutize = |p: &Path| -> PathBuf {
if p.as_os_str().is_empty() {
cwd.clone()
} else if p.is_absolute() {
p.to_path_buf()
} else {
cwd.join(p)
}
};
if let Some(p) = config_override {
if let Some(dir) = p.parent() {
for ancestor in dir.ancestors() {
if absolutize(ancestor).join("Cargo.toml").is_file() {
return Ok(absolutize(ancestor));
}
}
}
}
for ancestor in cwd.ancestors() {
if ancestor.join("Cargo.toml").is_file() {
return Ok(absolutize(ancestor));
}
}
anyhow::bail!("no Cargo.toml found from {}", cwd.display());
}