fallow_core/discover/
mod.rs1mod entry_points;
2mod infrastructure;
3mod parse_scripts;
4mod walk;
5
6use std::path::Path;
7
8use fallow_config::{PackageJson, ResolvedConfig};
9
10pub use fallow_types::discover::{DiscoveredFile, EntryPoint, EntryPointSource, FileId};
12
13pub use entry_points::{
15 CategorizedEntryPoints, compile_glob_set, discover_dynamically_loaded_entry_points,
16 discover_entry_points, discover_plugin_entry_point_sets, discover_plugin_entry_points,
17 discover_workspace_entry_points,
18};
19pub(crate) use entry_points::{
20 EntryPointDiscovery, discover_entry_points_with_warnings_from_pkg,
21 discover_workspace_entry_points_with_warnings_from_pkg, warn_skipped_entry_summary,
22};
23pub use infrastructure::discover_infrastructure_entry_points;
24pub use walk::{
25 HiddenDirScope, PRODUCTION_EXCLUDE_PATTERNS, SOURCE_EXTENSIONS, discover_files,
26 discover_files_with_additional_hidden_dirs,
27};
28
29#[must_use]
37pub fn collect_plugin_hidden_dir_scopes(
38 config: &ResolvedConfig,
39 root_pkg: Option<&PackageJson>,
40 workspaces: &[fallow_config::WorkspaceInfo],
41) -> Vec<HiddenDirScope> {
42 let registry = crate::plugins::PluginRegistry::new(config.external_plugins.clone());
43 let mut scopes = Vec::new();
44
45 if let Some(pkg) = root_pkg {
46 push_plugin_hidden_dir_scope(&mut scopes, ®istry, pkg, &config.root);
47 }
48
49 for ws in workspaces {
50 if let Ok(pkg) = PackageJson::load(&ws.root.join("package.json")) {
51 push_plugin_hidden_dir_scope(&mut scopes, ®istry, &pkg, &ws.root);
52 }
53 }
54
55 scopes
56}
57
58fn push_plugin_hidden_dir_scope(
59 scopes: &mut Vec<HiddenDirScope>,
60 registry: &crate::plugins::PluginRegistry,
61 pkg: &PackageJson,
62 root: &Path,
63) {
64 let dirs = registry.discovery_hidden_dirs(pkg, root);
65 if !dirs.is_empty() {
66 scopes.push(HiddenDirScope::new(root.to_path_buf(), dirs));
67 }
68}
69
70#[must_use]
78pub fn discover_files_with_plugin_scopes(config: &ResolvedConfig) -> Vec<DiscoveredFile> {
79 let root_pkg = PackageJson::load(&config.root.join("package.json")).ok();
80 let workspaces = fallow_config::discover_workspaces(&config.root);
81 let scopes = collect_plugin_hidden_dir_scopes(config, root_pkg.as_ref(), &workspaces);
82 discover_files_with_additional_hidden_dirs(config, &scopes)
83}
84
85const ALLOWED_HIDDEN_DIRS: &[&str] = &[
95 ".storybook",
96 ".vitepress",
97 ".well-known",
98 ".changeset",
99 ".github",
100];
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
109 fn allowed_hidden_dirs_count() {
110 assert_eq!(
112 ALLOWED_HIDDEN_DIRS.len(),
113 5,
114 "update tests when adding new allowed hidden dirs"
115 );
116 }
117
118 #[test]
119 fn allowed_hidden_dirs_all_start_with_dot() {
120 for dir in ALLOWED_HIDDEN_DIRS {
121 assert!(
122 dir.starts_with('.'),
123 "allowed hidden dir '{dir}' must start with '.'"
124 );
125 }
126 }
127
128 #[test]
129 fn allowed_hidden_dirs_no_duplicates() {
130 let mut seen = rustc_hash::FxHashSet::default();
131 for dir in ALLOWED_HIDDEN_DIRS {
132 assert!(seen.insert(*dir), "duplicate allowed hidden dir: {dir}");
133 }
134 }
135
136 #[test]
137 fn allowed_hidden_dirs_no_trailing_slash() {
138 for dir in ALLOWED_HIDDEN_DIRS {
139 assert!(
140 !dir.ends_with('/'),
141 "allowed hidden dir '{dir}' should not have trailing slash"
142 );
143 }
144 }
145
146 #[test]
149 fn file_id_re_exported() {
150 let id = FileId(42);
152 assert_eq!(id.0, 42);
153 }
154
155 #[test]
156 fn source_extensions_re_exported() {
157 assert!(SOURCE_EXTENSIONS.contains(&"ts"));
158 assert!(SOURCE_EXTENSIONS.contains(&"tsx"));
159 }
160
161 #[test]
162 fn compile_glob_set_re_exported() {
163 let result = compile_glob_set(&["**/*.ts".to_string()]);
164 assert!(result.is_some());
165 }
166}