use std::path::Path;
use indexmap::IndexMap;
use skif_config::{JsWorkspace, Override, PluginConfig};
use skif_js_plugins::{PluginContext, PluginOutput, resolve_plugins};
use crate::input::{BuildDir, InputRequest, InputSource, PathAlias, WorkspaceInputs};
use crate::setup::WorkspaceKeys;
#[derive(Debug, Clone)]
pub struct PluginSource {
plugins: IndexMap<String, PluginConfig>,
workspaces: IndexMap<String, JsWorkspace>,
workspace_keys: WorkspaceKeys,
scoped: Vec<Override>,
}
impl PluginSource {
#[must_use]
pub fn new(plugins: IndexMap<String, PluginConfig>) -> Self {
Self {
plugins,
workspaces: IndexMap::new(),
workspace_keys: WorkspaceKeys::default(),
scoped: Vec::new(),
}
}
#[must_use]
pub fn with_workspaces(mut self, workspaces: &IndexMap<String, JsWorkspace>) -> Self {
self.workspaces = workspaces
.iter()
.filter(|(_, workspace)| !workspace.plugins.is_empty())
.map(|(key, workspace)| (key.clone(), workspace.clone()))
.collect();
self.workspace_keys = WorkspaceKeys::new(&self.workspaces);
self
}
#[must_use]
pub fn with_overrides(mut self, overrides: &[Override]) -> Self {
self.scoped = overrides
.iter()
.filter(|scoped| !scoped.js.plugins.is_empty())
.cloned()
.collect();
self
}
fn plugins_for(&self, name: &str, dir: &Path, key: &str) -> IndexMap<String, PluginConfig> {
let mut plugins = self.plugins.clone();
if let Some(workspace) = self.workspace_keys.table(&self.workspaces, key) {
for (plugin, incoming) in &workspace.plugins {
let merged = merge(plugins.get(plugin), incoming);
plugins.insert(plugin.clone(), merged);
}
}
for scoped in self
.scoped
.iter()
.filter(|scoped| scoped.applies_to(Some((name, dir)), None))
{
for (plugin, incoming) in &scoped.js.plugins {
let merged = merge(plugins.get(plugin), incoming);
plugins.insert(plugin.clone(), merged);
}
}
plugins
}
}
fn merge(inherited: Option<&PluginConfig>, incoming: &PluginConfig) -> PluginConfig {
let inherited_enabled = match inherited {
Some(PluginConfig::Enabled(enabled)) => Some(*enabled),
Some(PluginConfig::Options(options)) => options.enabled,
_ => None,
};
match (incoming, inherited) {
(PluginConfig::Enabled(enabled), Some(PluginConfig::Options(options))) => {
PluginConfig::Options(options.clone().with_enabled(*enabled))
}
(PluginConfig::Options(options), _) if options.enabled.is_none() => {
let mut merged = options.clone();
merged.enabled = inherited_enabled;
PluginConfig::Options(merged)
}
_ => incoming.clone(),
}
}
fn scripts(request: &InputRequest<'_>) -> IndexMap<String, String> {
request
.package
.manifest
.value()
.get("scripts")
.and_then(serde_json::Value::as_object)
.map(|scripts| {
scripts
.iter()
.filter_map(|(name, command)| Some((name.clone(), command.as_str()?.to_owned())))
.collect()
})
.unwrap_or_default()
}
fn names(output: &[skif_js_plugins::DepRef], optional: bool) -> impl Iterator<Item = String> + '_ {
output
.iter()
.filter(move |dependency| dependency.optional == optional)
.map(|dependency| dependency.name.clone())
}
fn inputs(output: PluginOutput) -> WorkspaceInputs {
let entry = output
.entries
.iter()
.filter(|glob| !output.production_entries.contains(glob))
.cloned()
.collect();
let mut ignore_binaries = output.provided_binaries;
ignore_binaries.sort();
ignore_binaries.dedup();
WorkspaceInputs {
entry,
production_entry: output.production_entries,
project: output.project,
ignore: output.ignore,
ignore_dependencies: names(&output.referenced_dependencies, true).collect(),
ignore_binaries,
virtual_modules: output.virtual_modules,
dependencies: names(&output.referenced_dependencies, false).collect(),
binaries: output.referenced_binaries,
config_files: output.config_files,
aliases: output
.aliases
.into_iter()
.map(|alias| PathAlias {
find: alias.find,
target: alias.target,
})
.collect(),
build_dirs: output
.build_dirs
.into_iter()
.map(|dir| BuildDir {
out_dir: dir.out_dir,
root_dir: dir.root_dir,
})
.collect(),
conditions: output.conditions,
module_dirs: output.module_dirs,
extensions: output.extensions,
entries_incomplete: output.entries_incomplete,
}
}
fn inherited_dependencies(request: &InputRequest<'_>) -> Vec<String> {
let dir = &request.package.dir;
let mut names: Vec<String> = request
.workspace
.iter()
.filter(|(_, other)| other.dir != *dir && (other.is_root || dir.starts_with(&other.dir)))
.flat_map(|(_, other)| other.dependencies.iter().map(|entry| entry.name.clone()))
.chain(request.ancestor_dependencies.iter().cloned())
.collect();
names.sort();
names.dedup();
names
}
impl InputSource for PluginSource {
fn inputs(&self, request: &InputRequest<'_>) -> WorkspaceInputs {
let package = request.package;
let dependencies: Vec<String> = package.dependencies.iter().map(|entry| entry.name.clone()).collect();
let inherited = inherited_dependencies(request);
let scripts = scripts(request);
let dir = request.workspace.root.join(Path::new(&package.dir));
let key = if package.is_root {
".".to_owned()
} else {
package.dir.to_string_lossy().replace('\\', "/")
};
let plugins = self.plugins_for(
package.display_name(),
if package.is_root { &request.workspace.root } else { &dir },
&key,
);
let context = PluginContext {
dir: &dir,
is_root: package.is_root,
package_name: package.name.as_deref(),
dependencies: &dependencies,
inherited_dependencies: &inherited,
scripts: &scripts,
fs: request.fs,
plugins: &plugins,
};
resolve_plugins(&context)
.into_iter()
.fold(WorkspaceInputs::default(), |mut all, (_, output)| {
all.extend(inputs(output));
all
})
}
}