1#![allow(clippy::result_large_err)]
4
5use std::cell::{OnceCell, RefCell};
6use std::collections::BTreeMap;
7use std::path::PathBuf;
8use std::sync::Arc;
9
10use callisto_manifests::Manifest;
11use callisto_model::{CommandRunner, PackageId, Version};
12
13pub mod aggregate;
14pub mod apply;
15pub mod cascade;
16pub mod changed;
17pub mod commands;
18pub mod config;
19pub mod crosscheck;
20pub mod error;
21pub mod groups;
22pub mod identity;
23pub mod infer;
24pub mod locate;
25mod manifest_cache;
26pub mod napi;
27pub mod plan;
28pub mod resolver;
29pub mod tags;
30pub mod toposort;
31pub mod walk;
32
33pub use aggregate::{aggregate, load_changesets, Aggregation, LoadedChangeset, NamedBy};
34pub use apply::{apply_version_plan, ApplyOptions, ApplyOutcome};
35pub use cascade::{
36 cascade_action, coverage, rewrite_spec, run_cascade, CascadeDecision, CascadeInput,
37 CascadeOutcome, DepWriteTarget, RewriteKey, RewriteOutcome, SpecRewrite,
38};
39pub use config::{load as load_config, GroupDef, GroupTable, ResolvedConfig};
40pub use error::{ConfigError, GraphError};
41pub use groups::{fixed_group_target, pre_mutation_checks, GroupCheckOutcome};
42pub use identity::{IdentityIndex, IdentityResolver};
43pub use infer::{InferenceOutcome, InferenceWindowSpec, NoInference, SeverityInference};
44pub use locate::{find_workspace_root, IgnoreWalkLocator, LocateError, ProjectLocator};
45pub use napi::{napi_drift, role_to_triple, triple_to_role, NapiTargetsIndex};
46pub use plan::{PlannedBump, VersionPlan, VersionWriteTarget};
47pub use resolver::{DependencyResolver, ManifestWalkResolver};
48pub use tags::{last_tag_for, TagIndex};
49pub use toposort::toposort_impl;
50
51pub struct Workspace<'a, R: CommandRunner, D: DependencyResolver = ManifestWalkResolver> {
52 pub root: PathBuf,
53 pub config: ResolvedConfig,
54 pub graph: D,
55 pub tags: OnceCell<TagIndex>,
73 pub runner: &'a R,
74 pub manifest_cache: RefCell<BTreeMap<PathBuf, Arc<dyn Manifest>>>,
82}
83
84impl<'a, R: CommandRunner> Workspace<'a, R, ManifestWalkResolver> {
85 pub fn load<L: ProjectLocator>(
86 root: PathBuf,
87 locator: &L,
88 runner: &'a R,
89 ) -> Result<Self, GraphError> {
90 let mut config = config::load(&root)?;
91 let manifest_cache: RefCell<BTreeMap<PathBuf, Arc<dyn Manifest>>> =
92 RefCell::new(BTreeMap::new());
93 let graph = ManifestWalkResolver::build(&root, locator, runner, &config, &manifest_cache)?;
94
95 config.groups = GroupTable::resolve(&config.raw_groups, graph.identity())?;
96
97 Ok(Workspace {
98 root,
99 config,
100 graph,
101 tags: OnceCell::new(),
102 runner,
103 manifest_cache,
104 })
105 }
106}
107
108impl<'a, R: CommandRunner, D: DependencyResolver> Workspace<'a, R, D> {
109 pub fn tags(&self) -> Result<&TagIndex, GraphError> {
114 if let Some(existing) = self.tags.get() {
115 return Ok(existing);
116 }
117 let built = TagIndex::build(self.runner, &self.root, &self.graph, &self.config)?;
118 self.tags.set(built).ok();
125 Ok(self
126 .tags
127 .get()
128 .expect("tags was just set above, or already set by a prior call"))
129 }
130
131 pub fn base_versions(&self) -> Result<BTreeMap<PackageId, Version>, GraphError> {
132 let cargo_workspace = if self.root.join("Cargo.toml").exists() {
133 if let Ok(resolver) =
134 callisto_manifests::WorkspaceCargoResolver::load(&self.root.join("Cargo.toml"))
135 {
136 resolver.inheritance().ok().map(std::sync::Arc::new)
137 } else {
138 None
139 }
140 } else {
141 None
142 };
143 let npm_workspace_kind = callisto_manifests::detect_npm_workspace_kind(&self.root)
144 .ok()
145 .flatten();
146 let ctx = callisto_manifests::OpenContext {
147 workspace_root: &self.root,
148 cargo_workspace,
149 npm_workspace_kind,
150 };
151
152 let mut versions = BTreeMap::new();
153 for pkg in self.graph.packages() {
154 let mut found_version = None;
155 for decl in &pkg.manifests {
156 if decl.role == callisto_model::ManifestRole::Canonical {
157 let handle = manifest_cache::open_cached(&self.manifest_cache, decl, &ctx)?;
158 let v = handle.current_version()?;
159 found_version = Some(v);
160 break;
161 }
162 }
163 if let Some(version) = found_version {
164 versions.insert(pkg.id.clone(), version);
165 } else {
166 return Err(GraphError::Manifest(
167 callisto_model::ManifestError::MissingField {
168 path: pkg
169 .manifests
170 .first()
171 .map(|m| m.path.clone())
172 .unwrap_or_default(),
173 field: "version",
174 },
175 ));
176 }
177 }
178 Ok(versions)
179 }
180
181 pub fn pre_json_key<'b>(&self, id: &'b PackageId) -> Result<&'b str, GraphError> {
182 Ok(id.name())
183 }
184
185 pub fn initial_versions(&self) -> Result<Vec<(String, Version)>, GraphError> {
186 let base = self.base_versions()?;
187 Ok(base
188 .into_iter()
189 .map(|(id, v)| (id.name().to_string(), v))
190 .collect())
191 }
192}