1#![allow(clippy::result_large_err)]
4
5use std::collections::BTreeMap;
6use std::path::PathBuf;
7
8use callisto_model::{CommandRunner, PackageId, Version};
9
10pub mod aggregate;
11pub mod apply;
12pub mod cascade;
13pub mod changed;
14pub mod commands;
15pub mod config;
16pub mod crosscheck;
17pub mod error;
18pub mod groups;
19pub mod identity;
20pub mod infer;
21pub mod locate;
22pub mod napi;
23pub mod plan;
24pub mod resolver;
25pub mod tags;
26pub mod toposort;
27pub mod walk;
28
29pub use aggregate::{aggregate, load_changesets, Aggregation, LoadedChangeset, NamedBy};
30pub use apply::{apply_version_plan, ApplyOptions, ApplyOutcome};
31pub use cascade::{
32 cascade_action, coverage, rewrite_spec, run_cascade, CascadeDecision, CascadeInput,
33 CascadeOutcome, DepWriteTarget, RewriteKey, RewriteOutcome, SpecRewrite,
34};
35pub use config::{load as load_config, GroupDef, GroupTable, ResolvedConfig};
36pub use error::{ConfigError, GraphError};
37pub use groups::{fixed_group_target, pre_mutation_checks, GroupCheckOutcome};
38pub use identity::{IdentityIndex, IdentityResolver};
39pub use infer::{InferenceOutcome, InferenceWindowSpec, NoInference, SeverityInference};
40pub use locate::{find_workspace_root, IgnoreWalkLocator, LocateError, ProjectLocator};
41pub use napi::{napi_drift, role_to_triple, triple_to_role, NapiTargetsIndex};
42pub use plan::{PlannedBump, VersionPlan, VersionWriteTarget};
43pub use resolver::{DependencyResolver, ManifestWalkResolver};
44pub use tags::{last_tag_for, TagIndex};
45pub use toposort::toposort_impl;
46
47pub struct Workspace<'a, R: CommandRunner, D: DependencyResolver = ManifestWalkResolver> {
48 pub root: PathBuf,
49 pub config: ResolvedConfig,
50 pub graph: D,
51 pub tags: TagIndex,
52 pub runner: &'a R,
53}
54
55impl<'a, R: CommandRunner> Workspace<'a, R, ManifestWalkResolver> {
56 pub fn load<L: ProjectLocator>(
57 root: PathBuf,
58 locator: &L,
59 runner: &'a R,
60 ) -> Result<Self, GraphError> {
61 let config = config::load(&root)?;
62 let graph = ManifestWalkResolver::build(&root, locator, runner, &config)?;
63 let tags = TagIndex::build(runner, &root, &graph, &config)?;
64
65 Ok(Workspace {
66 root,
67 config,
68 graph,
69 tags,
70 runner,
71 })
72 }
73}
74
75impl<'a, R: CommandRunner, D: DependencyResolver> Workspace<'a, R, D> {
76 pub fn base_versions(&self) -> Result<BTreeMap<PackageId, Version>, GraphError> {
77 let cargo_workspace = if self.root.join("Cargo.toml").exists() {
78 if let Ok(resolver) =
79 callisto_manifests::WorkspaceCargoResolver::load(&self.root.join("Cargo.toml"))
80 {
81 resolver.inheritance().ok().map(std::sync::Arc::new)
82 } else {
83 None
84 }
85 } else {
86 None
87 };
88 let npm_workspace_kind = callisto_manifests::detect_npm_workspace_kind(&self.root)
89 .ok()
90 .flatten();
91 let ctx = callisto_manifests::OpenContext {
92 workspace_root: &self.root,
93 cargo_workspace,
94 npm_workspace_kind,
95 };
96
97 let mut versions = BTreeMap::new();
98 for pkg in self.graph.packages() {
99 let mut found_version = None;
100 for decl in &pkg.manifests {
101 if decl.role == callisto_model::ManifestRole::Canonical {
102 let handle = callisto_manifests::open(decl, &ctx)?;
103 let v = handle.current_version()?;
104 found_version = Some(v);
105 break;
106 }
107 }
108 if let Some(version) = found_version {
109 versions.insert(pkg.id.clone(), version);
110 } else {
111 return Err(GraphError::Manifest(
112 callisto_model::ManifestError::MissingField {
113 path: pkg
114 .manifests
115 .first()
116 .map(|m| m.path.clone())
117 .unwrap_or_default(),
118 field: "version",
119 },
120 ));
121 }
122 }
123 Ok(versions)
124 }
125
126 pub fn pre_json_key<'b>(&self, id: &'b PackageId) -> Result<&'b str, GraphError> {
127 Ok(id.name())
128 }
129
130 pub fn initial_versions(&self) -> Result<Vec<(String, Version)>, GraphError> {
131 let base = self.base_versions()?;
132 Ok(base
133 .into_iter()
134 .map(|(id, v)| (id.name().to_string(), v))
135 .collect())
136 }
137}