Skip to main content

fallow_engine/
plugins.rs

1//! Plugin registry helpers and types exposed through the engine boundary.
2
3use std::path::Path;
4
5use fallow_config::{ExternalPluginDef, PackageJson};
6
7use crate::core_backend;
8
9/// External-plugin dry-run primitives for the CLI's `plugin-check` command.
10pub use crate::core_backend::{
11    CheckWarning, ManifestResult, RuleReport, WarningKind, check_manifest_entries,
12    is_external_plugin_active,
13};
14
15/// Built-in plugin name roster.
16pub mod registry {
17    use crate::core_backend;
18
19    /// Names of every built-in framework plugin in registry order.
20    ///
21    /// Delegates to the core registry rather than mirroring it. A hand-kept
22    /// copy drifted here once already, silently omitting `deno`, and nothing
23    /// pinned the two together.
24    #[must_use]
25    pub fn builtin_plugin_names() -> Vec<&'static str> {
26        core_backend::builtin_plugin_names()
27    }
28}
29
30/// Aggregated results from all active plugins for a project.
31#[derive(Debug, Clone, Default)]
32pub struct AggregatedPluginResult {
33    inner: core_backend::BackendAggregatedPluginResult,
34}
35
36impl AggregatedPluginResult {
37    /// Names of active plugins.
38    #[must_use]
39    pub fn active_plugins(&self) -> &[String] {
40        self.inner.active_plugins()
41    }
42
43    /// The plugin stage's advisories (`plugin-config-unreadable`,
44    /// `plugin-effect-not-modeled`), with paths rendered against `root`.
45    #[must_use]
46    pub fn plugin_diagnostics(&self, root: &Path) -> Vec<fallow_config::WorkspaceDiagnostic> {
47        self.inner.plugin_diagnostics(root)
48    }
49
50    pub(crate) fn backend(&self) -> &core_backend::BackendAggregatedPluginResult {
51        &self.inner
52    }
53}
54
55impl From<core_backend::BackendAggregatedPluginResult> for AggregatedPluginResult {
56    fn from(inner: core_backend::BackendAggregatedPluginResult) -> Self {
57        Self { inner }
58    }
59}
60
61/// Registry of all available plugins.
62pub struct PluginRegistry {
63    inner: core_backend::BackendPluginRegistry,
64}
65
66impl PluginRegistry {
67    /// Create a registry with all built-in plugins and optional external plugins.
68    #[must_use]
69    pub(crate) fn new(external: Vec<ExternalPluginDef>) -> Self {
70        Self {
71            inner: core_backend::BackendPluginRegistry::new(external),
72        }
73    }
74
75    /// Hidden directory names that should be traversed before full plugin execution.
76    #[must_use]
77    pub(crate) fn discovery_hidden_dirs(&self, pkg: &PackageJson, root: &Path) -> Vec<String> {
78        self.inner.discovery_hidden_dirs(pkg, root)
79    }
80}
81
82impl Default for PluginRegistry {
83    fn default() -> Self {
84        Self::new(vec![])
85    }
86}
87
88#[cfg(test)]
89mod roster_tests {
90    /// The engine mirrored core's roster by hand and the copy drifted, omitting
91    /// `deno`. Delegation removes the copy, so there is nothing left to diverge;
92    /// this pins the name the drift lost and that the roster is really populated.
93    ///
94    /// The roster is read through `core_backend`, not from `fallow_core`
95    /// directly, because the boundary guard requires every crossing to go
96    /// through that adapter.
97    #[test]
98    fn roster_carries_every_registered_plugin() {
99        let names = super::registry::builtin_plugin_names();
100        assert!(
101            names.contains(&"deno"),
102            "deno is registered in core and must reach the roster"
103        );
104        assert!(
105            names.len() > 100,
106            "roster looks truncated: {} names",
107            names.len()
108        );
109    }
110}