use std::path::Path;
use super::config::{check_flat, check_flat_exact_int, check_yaml, read_flat_config, YamlCheck};
use super::detect::get_delay_days;
use super::types::{mark_unsupported, unsupported_if_configured, CheckStatus, Recommendation};
use super::version::version_at_least;
pub fn scan_project(path: &Path, version: &str) -> Vec<Recommendation> {
let days = get_delay_days();
let minutes = days.saturating_mul(24).saturating_mul(60);
let cfg = read_flat_config(path);
let release_age = check_flat_exact_int(
path,
&cfg,
"minimum-release-age",
minutes,
&format!("Delay new versions by {days} days"),
);
let ignore_scripts = check_flat(
path,
&cfg,
"ignore-scripts",
"true",
"Block malicious install scripts",
);
if version_at_least(version, 11, 0) {
let redirect = |yaml_key: &str| {
format!(
"ignored in .npmrc by pnpm \u{2265} 11 — set {yaml_key} in \
pnpm-workspace.yaml or the global config.yaml"
)
};
return [
(release_age, "minimumReleaseAge"),
(ignore_scripts, "ignoreScripts"),
]
.into_iter()
.filter(|(rec, _)| matches!(rec.status, CheckStatus::Ok(_) | CheckStatus::WrongValue(_)))
.map(|(rec, yaml_key)| mark_unsupported(rec, redirect(yaml_key)))
.collect();
}
let release_age = if version_at_least(version, 10, 16) {
release_age
} else {
unsupported_if_configured(release_age, "pnpm", 10, 16, version)
};
vec![release_age, ignore_scripts]
}
pub fn uses_yaml_config(version: &str) -> bool {
version_at_least(version, 11, 0)
}
pub fn scan_global(path: &Path, version: &str) -> Vec<Recommendation> {
let days = get_delay_days();
let minutes = days.saturating_mul(24).saturating_mul(60);
if uses_yaml_config(version) {
scan_global_yaml(path, version, days, minutes)
} else {
scan_global_rc(path, version, days, minutes)
}
}
fn scan_global_yaml(path: &Path, version: &str, days: u64, minutes: u64) -> Vec<Recommendation> {
let g = |min_ver| VersionGate {
path,
version,
min_ver,
};
vec![
g((10, 16)).yaml(
"minimumReleaseAge",
&minutes.to_string(),
&format!("Delay new versions by {days} days"),
YamlCheck::ExactInt(minutes),
),
g((10, 26)).yaml(
"blockExoticSubdeps",
"true",
"Block untrusted transitive deps",
YamlCheck::Exact,
),
g((10, 21)).yaml(
"trustPolicy",
"no-downgrade",
"Block provenance downgrades",
YamlCheck::Exact,
),
g((10, 3)).yaml(
"strictDepBuilds",
"true",
"Fail on unreviewed build scripts",
YamlCheck::Exact,
),
g((10, 16)).yaml(
"ignoreScripts",
"true",
"Block malicious install scripts",
YamlCheck::Exact,
),
]
}
fn scan_global_rc(path: &Path, version: &str, days: u64, minutes: u64) -> Vec<Recommendation> {
let cfg = read_flat_config(path);
let g = |min_ver| VersionGate {
path,
version,
min_ver,
};
vec![
g((10, 16)).flat_exact_int(
&cfg,
"minimum-release-age",
minutes,
&format!("Delay new versions by {days} days"),
),
g((10, 26)).flat_exact(
&cfg,
"block-exotic-subdeps",
"true",
"Block untrusted transitive deps",
),
g((10, 21)).flat_exact(
&cfg,
"trust-policy",
"no-downgrade",
"Block provenance downgrades",
),
g((10, 3)).flat_exact(
&cfg,
"strict-dep-builds",
"true",
"Fail on unreviewed build scripts",
),
check_flat(
path,
&cfg,
"ignore-scripts",
"true",
"Block malicious install scripts",
),
]
}
pub fn scan_workspace(path: &Path, version: &str) -> Vec<Recommendation> {
let days = get_delay_days();
let minutes = days.saturating_mul(24).saturating_mul(60);
let g = |min_ver| VersionGate {
path,
version,
min_ver,
};
vec![
g((10, 16)).yaml(
"minimumReleaseAge",
&minutes.to_string(),
&format!("Delay new versions by {days} days"),
YamlCheck::ExactInt(minutes),
),
g((10, 26)).yaml(
"blockExoticSubdeps",
"true",
"Block untrusted transitive deps",
YamlCheck::Exact,
),
g((10, 21)).yaml(
"trustPolicy",
"no-downgrade",
"Block provenance downgrades",
YamlCheck::Exact,
),
g((10, 3)).yaml(
"strictDepBuilds",
"true",
"Fail on unreviewed build scripts",
YamlCheck::Exact,
),
g((10, 16)).yaml(
"ignoreScripts",
"true",
"Block malicious install scripts",
YamlCheck::Exact,
),
]
}
struct VersionGate<'a> {
path: &'a Path,
version: &'a str,
min_ver: (u64, u64),
}
impl VersionGate<'_> {
fn yaml(&self, key: &str, expected: &str, desc: &str, mode: YamlCheck) -> Recommendation {
let rec = check_yaml(self.path, key, expected, desc, mode);
if version_at_least(self.version, self.min_ver.0, self.min_ver.1) {
rec
} else {
unsupported_if_configured(rec, "pnpm", self.min_ver.0, self.min_ver.1, self.version)
}
}
fn flat_exact(
&self,
cfg: &std::collections::HashMap<String, String>,
key: &str,
expected: &str,
desc: &str,
) -> Recommendation {
let rec = check_flat(self.path, cfg, key, expected, desc);
if version_at_least(self.version, self.min_ver.0, self.min_ver.1) {
rec
} else {
unsupported_if_configured(rec, "pnpm", self.min_ver.0, self.min_ver.1, self.version)
}
}
fn flat_exact_int(
&self,
cfg: &std::collections::HashMap<String, String>,
key: &str,
expected: u64,
desc: &str,
) -> Recommendation {
let rec = check_flat_exact_int(self.path, cfg, key, expected, desc);
if version_at_least(self.version, self.min_ver.0, self.min_ver.1) {
rec
} else {
unsupported_if_configured(rec, "pnpm", self.min_ver.0, self.min_ver.1, self.version)
}
}
}