use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use crate::chain::FailurePolicy;
use crate::config::LoadedConfig;
use crate::tool::node::OnFail;
use crate::types::{
DetectionWarning, Ecosystem, PackageManager, ProjectContext, TaskRunner, TaskSource,
};
pub(crate) struct Resolver<'ctx> {
pub(super) ctx: &'ctx ProjectContext,
pub(super) overrides: &'ctx ResolutionOverrides,
}
#[derive(Debug, Clone, Default)]
#[allow(
clippy::struct_excessive_bools,
reason = "settings bag of independent CLI/env/config toggles; an enum state machine would \
obscure them, not clarify"
)]
pub(crate) struct ResolutionOverrides {
pub pm: Option<PmOverride>,
pub pm_by_ecosystem: HashMap<Ecosystem, PmOverride>,
pub runner: Option<RunnerOverride>,
pub prefer_runners: Vec<TaskRunner>,
pub prefer_sources: Vec<TaskSource>,
pub task_source_overrides: BTreeMap<String, Vec<TaskSource>>,
pub fallback: FallbackPolicy,
pub on_mismatch: MismatchPolicy,
pub no_warnings: bool,
pub quiet: bool,
pub explain: bool,
pub failure_policy: FailurePolicy,
pub group_output: bool,
pub github_group_parallel: bool,
pub parallel_grouped: bool,
pub install_pms: Vec<PackageManager>,
pub script_policy: ScriptPolicy,
pub parent_group_open: bool,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum FallbackPolicy {
#[default]
Probe,
Npm,
Error,
}
impl FallbackPolicy {
pub(crate) const ALL: [Self; 3] = [Self::Probe, Self::Npm, Self::Error];
pub(crate) const fn label(self) -> &'static str {
match self {
Self::Probe => "probe",
Self::Npm => "npm",
Self::Error => "error",
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ScriptPolicy {
#[default]
Default,
Deny,
Allow,
}
impl ScriptPolicy {
pub(crate) const SETTABLE: [Self; 2] = [Self::Deny, Self::Allow];
pub(crate) const fn label(self) -> Option<&'static str> {
match self {
Self::Default => None,
Self::Deny => Some("deny"),
Self::Allow => Some("allow"),
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum MismatchPolicy {
#[default]
Warn,
Ignore,
Error,
}
impl MismatchPolicy {
pub(crate) const ALL: [Self; 3] = [Self::Warn, Self::Ignore, Self::Error];
pub(crate) const fn label(self) -> &'static str {
match self {
Self::Warn => "warn",
Self::Ignore => "ignore",
Self::Error => "error",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct PmOverride {
pub pm: PackageManager,
pub origin: OverrideOrigin,
}
#[derive(Debug, Clone)]
pub(crate) struct RunnerOverride {
pub runner: TaskRunner,
#[allow(
dead_code,
reason = "consumed by --explain in Phase 6; kept on the type for future trace renderers"
)]
pub origin: OverrideOrigin,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum OverrideOrigin {
CliFlag,
EnvVar,
ConfigFile {
path: PathBuf,
},
}
impl OverrideOrigin {
pub(crate) fn describe_pm_source(&self) -> String {
match self {
Self::CliFlag => "via --pm (CLI override)".to_string(),
Self::EnvVar => "via RUNNER_PM (environment)".to_string(),
Self::ConfigFile { path } => format!("via runner.toml at {}", path.display()),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct ResolvedPm {
pub pm: PackageManager,
pub via: ResolutionStep,
pub warnings: Vec<DetectionWarning>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ResolutionStep {
Override(OverrideOrigin),
ManifestPackageManager,
ManifestDevEngines {
on_fail: OnFail,
},
Lockfile,
PathProbe {
binary: PathBuf,
},
LegacyNpmFallback,
}
#[derive(Debug, Default)]
pub(crate) struct OverrideSources<'a> {
pub pm: SourceValue<'a>,
pub runner: SourceValue<'a>,
pub fallback: SourceValue<'a>,
pub on_mismatch: SourceValue<'a>,
pub no_warnings: ExplainSource<'a>,
pub quiet: ExplainSource<'a>,
pub explain: ExplainSource<'a>,
pub keep_going: ExplainSource<'a>,
pub kill_on_fail: ExplainSource<'a>,
pub install_pms: SourceValue<'a>,
pub install_scripts: SourceValue<'a>,
pub group_active: Option<&'a str>,
pub config: Option<&'a LoadedConfig>,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct SourceValue<'a> {
pub cli: Option<&'a str>,
pub env: Option<&'a str>,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct DiagnosticFlags {
pub no_warnings: bool,
pub quiet: bool,
pub explain: bool,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct ExplainSource<'a> {
pub cli: bool,
pub env: Option<&'a str>,
}
impl ResolvedPm {
pub(crate) fn describe(&self) -> String {
match &self.via {
ResolutionStep::Override(origin) => {
format!("{} {}", self.pm.label(), origin.describe_pm_source())
}
ResolutionStep::ManifestPackageManager => {
format!("{} via package.json \"packageManager\"", self.pm.label())
}
ResolutionStep::ManifestDevEngines { on_fail } => format!(
"{} via package.json \"devEngines.packageManager\" (onFail={on_fail:?})",
self.pm.label(),
),
ResolutionStep::Lockfile => {
format!("{} via detected lockfile", self.pm.label())
}
ResolutionStep::PathProbe { binary } => {
format!("{} via PATH probe at {}", self.pm.label(), binary.display())
}
ResolutionStep::LegacyNpmFallback => {
format!("{} via --fallback=npm (legacy)", self.pm.label())
}
}
}
}