fallow_engine/
list_inventory.rs1use std::path::{Path, PathBuf};
4
5use fallow_config::{ResolvedConfig, WorkspaceInfo};
6
7use crate::{
8 discover::{DiscoveredFile, EntryPoint},
9 plugins::{AggregatedPluginResult, PluginRegistry, registry::PluginRegexValidationError},
10};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ListInventoryError {
15 PluginRegex(Vec<PluginRegexValidationError>),
17}
18
19pub fn collect_active_plugins(
28 root: &Path,
29 config: &ResolvedConfig,
30 discovered: &[DiscoveredFile],
31 workspaces: &[WorkspaceInfo],
32) -> Result<AggregatedPluginResult, ListInventoryError> {
33 let file_paths = discovered
34 .iter()
35 .map(|file| file.path.clone())
36 .collect::<Vec<_>>();
37 let registry = PluginRegistry::new(config.external_plugins.clone());
38 let mut result = run_package_plugins(®istry, root, &file_paths)?.unwrap_or_default();
39
40 for workspace in workspaces {
41 let Some(workspace_result) = run_package_plugins(®istry, &workspace.root, &file_paths)?
42 else {
43 continue;
44 };
45 result.merge_active_plugins_from(&workspace_result);
46 }
47
48 Ok(result)
49}
50
51#[must_use]
53pub fn collect_entry_points(
54 config: &ResolvedConfig,
55 discovered: &[DiscoveredFile],
56 workspaces: &[WorkspaceInfo],
57 plugin_result: Option<&AggregatedPluginResult>,
58) -> Vec<EntryPoint> {
59 let mut entries = crate::discover::discover_entry_points(config, discovered);
60 for workspace in workspaces {
61 entries.extend(crate::discover::discover_workspace_entry_points(
62 &workspace.root,
63 config,
64 discovered,
65 ));
66 }
67 if let Some(plugin_result) = plugin_result {
68 entries.extend(crate::discover::discover_plugin_entry_points(
69 plugin_result,
70 config,
71 discovered,
72 ));
73 }
74 entries
75}
76
77fn run_package_plugins(
78 registry: &PluginRegistry,
79 package_root: &Path,
80 file_paths: &[PathBuf],
81) -> Result<Option<AggregatedPluginResult>, ListInventoryError> {
82 let Some(package) = fallow_config::load_dir_package_json(package_root) else {
83 return Ok(None);
84 };
85 registry
86 .try_run(&package, package_root, file_paths)
87 .map(Some)
88 .map_err(ListInventoryError::PluginRegex)
89}
90
91#[cfg(test)]
92mod tests {
93 use std::path::Path;
94
95 use fallow_config::{FallowConfig, WorkspaceInfo};
96 use fallow_types::output_format::OutputFormat;
97
98 use super::*;
99 use crate::discover::{EntryPointSource, FileId};
100
101 #[test]
102 fn entry_points_include_root_and_workspace_entries() {
103 let temp = tempfile::tempdir().expect("tempdir");
104 let root = temp.path();
105 let config = FallowConfig::default().resolve(
106 root.to_path_buf(),
107 OutputFormat::Json,
108 1,
109 false,
110 true,
111 None,
112 );
113 let workspace = WorkspaceInfo {
114 root: root.join("packages/web"),
115 name: "web".to_owned(),
116 is_internal_dependency: false,
117 };
118 let discovered = vec![
119 DiscoveredFile {
120 id: FileId(0),
121 path: root.join("src/main.ts"),
122 size_bytes: 0,
123 },
124 DiscoveredFile {
125 id: FileId(1),
126 path: root.join("packages/web/src/index.ts"),
127 size_bytes: 0,
128 },
129 ];
130
131 let entries = collect_entry_points(&config, &discovered, &[workspace], None);
132
133 assert!(
134 entries
135 .iter()
136 .any(|entry| entry.path.ends_with("src/main.ts"))
137 );
138 assert!(
139 entries
140 .iter()
141 .any(|entry| entry.path.ends_with("packages/web/src/index.ts"))
142 );
143 }
144
145 #[test]
146 fn active_plugins_ignores_missing_package_manifests() {
147 let config = FallowConfig::default().resolve(
148 Path::new("/missing-project").to_path_buf(),
149 OutputFormat::Json,
150 1,
151 false,
152 true,
153 None,
154 );
155 let result = collect_active_plugins(Path::new("/missing-project"), &config, &[], &[])
156 .expect("missing package should not fail");
157
158 assert!(result.active_plugins().is_empty());
159 }
160
161 #[test]
162 fn entry_points_accept_plugin_result() {
163 let config = FallowConfig::default().resolve(
164 Path::new("/project").to_path_buf(),
165 OutputFormat::Json,
166 1,
167 false,
168 true,
169 None,
170 );
171 let discovered = Vec::new();
172
173 let entries = collect_entry_points(&config, &discovered, &[], None);
174
175 assert!(
176 entries
177 .iter()
178 .all(|entry| !matches!(entry.source, EntryPointSource::Plugin { .. }))
179 );
180 }
181}