use Effect::{Destructive, Read, Write};
use usage_rs::spec::{CommandOverlay, Effect};
pub const EFFECTS: &[(&str, Effect)] = &[
("__node-gyp-bootstrap", Write),
("access", Read),
("access get", Read),
("access get status", Read),
("access grant", Write),
("access list", Read),
("access list collaborators", Read),
("access list packages", Read),
("access ls", Read),
("access revoke", Destructive),
("access set", Write),
("activate", Write),
("add", Write),
("approve-builds", Write),
("audit", Read),
("bin", Read),
("bugs", Read),
("cache", Read),
("cache delete", Write),
("cache list", Read),
("cache list-registries", Read),
("cache path", Read),
("cache prune", Write),
("cache view", Read),
("cat-file", Read),
("cat-index", Read),
("check", Read),
("ci", Write),
("clean", Write),
("completion", Read),
("config", Read),
("config delete", Destructive),
("config explain", Read),
("config find", Read),
("config get", Read),
("config list", Read),
("config set", Write),
("config tui", Write),
("dedupe", Write),
("deploy", Write),
("deprecate", Write),
("deprecations", Read),
("diag", Read),
("diag analyze", Read),
("diag compare", Read),
("dist-tag", Read),
("dist-tag add", Write),
("dist-tag ls", Read),
("dist-tag rm", Destructive),
("doctor", Read),
("fetch", Write),
("find-hash", Read),
("get", Read),
("ignored-builds", Read),
("import", Write),
("init", Write),
("install", Write),
("la", Read),
("licenses", Read),
("link", Write),
("list", Read),
("ll", Read),
("login", Write),
("logout", Destructive),
("outdated", Read),
("pack", Write),
("patch", Write),
("patch-commit", Write),
("patch-remove", Destructive),
("peers", Read),
("peers check", Read),
("prefix", Read),
("prune", Write),
("purge", Write),
("query", Read),
("rebuild", Write),
("remove", Destructive),
("root", Read),
("runtime", Read),
("runtime list", Read),
("runtime set", Write),
("sbom", Read),
("sponsors", Read),
("store", Read),
("store add", Write),
("store path", Read),
("store prune", Write),
("store status", Read),
("trust", Read),
("trust check", Read),
("undeprecate", Write),
("unlink", Write),
("unpublish", Destructive),
("update", Write),
("version", Write),
("view", Read),
("why", Read),
("owner", Read),
("pkg", Read),
("search", Read),
("set-script", Read),
("stage", Read),
("token", Read),
("whoami", Read),
("set", Write),
];
pub const FEATURE_EFFECTS: &[(&str, Effect)] = &[
#[cfg(feature = "publish")]
("publish", Write),
];
#[cfg(test)]
pub const UNCLASSIFIED: &[(&str, &str)] = &[
(
"create",
"runs a create-* starter kit fetched from the registry",
),
("dlx", "fetches a package and runs its binary"),
("exec", "runs a locally installed binary"),
(
"install-test",
"installs, then runs the package's test script",
),
("node", "runs Node.js with whatever arguments are given"),
("recursive", "runs another command across every workspace"),
("restart", "runs the package's restart script"),
("run", "runs a script defined in package.json"),
("start", "runs the package's start script"),
("stop", "runs the package's stop script"),
("test", "runs the package's test script"),
];
pub fn overlays() -> Vec<CommandOverlay<'static>> {
EFFECTS
.iter()
.chain(FEATURE_EFFECTS)
.map(|(path, effect)| CommandOverlay::effect(path, *effect))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
fn all_commands() -> Vec<String> {
let mut out = vec![];
collect(crate::spec().root, &mut vec![], &mut out);
out
}
fn collect(
cmd: &usage_rs::spec::CommandMeta<'_>,
path: &mut Vec<String>,
out: &mut Vec<String>,
) {
for sub in cmd.subcommands {
path.push(sub.cmd.name.to_string());
out.push(path.join(" "));
collect(sub, path, out);
path.pop();
}
}
fn classified() -> HashSet<&'static str> {
EFFECTS
.iter()
.chain(FEATURE_EFFECTS)
.map(|(name, _)| *name)
.chain(UNCLASSIFIED.iter().map(|(name, _)| *name))
.collect()
}
#[test]
fn every_command_is_classified() {
let known = classified();
let missing: Vec<String> = all_commands()
.into_iter()
.filter(|cmd| !known.contains(cmd.as_str()))
.collect();
assert!(
missing.is_empty(),
"these commands have no entry in EFFECTS or UNCLASSIFIED \
(crates/aube/src/command_effects.rs) — decide whether each is \
read, write, destructive, or genuinely unclassifiable:\n {}",
missing.join("\n ")
);
}
#[test]
fn no_classification_refers_to_a_missing_command() {
let present: HashSet<String> = all_commands().into_iter().collect();
let stale: Vec<&str> = classified()
.into_iter()
.filter(|name| !present.contains(*name))
.collect();
assert!(
stale.is_empty(),
"these entries no longer match a command:\n {}",
stale.join("\n ")
);
}
#[test]
fn classifications_are_not_duplicated() {
let mut seen = HashSet::new();
for name in EFFECTS
.iter()
.chain(FEATURE_EFFECTS)
.map(|(n, _)| *n)
.chain(UNCLASSIFIED.iter().map(|(n, _)| *n))
{
assert!(seen.insert(name), "{name} is classified twice");
}
}
#[test]
fn installing_completion_scripts_is_a_write() {
let completion = crate::Cli::spec()
.root
.subcommands
.iter()
.find(|command| command.cmd.name == "completion")
.expect("completion");
let flag = |name: &str| {
completion
.flags
.iter()
.find(|f| f.flag.name == name)
.unwrap_or_else(|| panic!("`aube completion` has no --{name}"))
};
assert_eq!(flag("install").effect, Some(usage_rs::spec::Effect::Write));
assert_eq!(flag("force").effect, Some(usage_rs::spec::Effect::Write));
}
#[test]
fn overlays_annotate_the_spec() {
let kdl = crate::usage_kdl();
for (command, effect) in [
("unpublish", "destructive"),
("list", "read"),
("install", "write"),
("rm", "destructive"),
] {
assert!(
kdl.lines().any(|line| {
line.trim_start().starts_with(&format!("cmd {command} "))
&& line.contains(&format!("effect={effect}"))
}),
"{command} is missing effect={effect} from the emitted spec"
);
}
}
}