From 77654058c2d0ef937087a64a5e557ff850e38375 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Thu, 23 Jul 2026 18:24:10 -0400 Subject: [PATCH] =?UTF-8?q?feat(cli):=20hidden=20--mode=20aliases=20?= =?UTF-8?q?=E2=80=94=20`vendor`=20for=20vendored,=20`host`=20for=20hosted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ergonomic (deliberately undocumented) clap value aliases on ScanMode: `--mode vendor` == `--mode vendored`, `--mode host` == `--mode hosted`. Help output is unchanged — a test renders scan's help and asserts the possible-values segment is exactly `hosted, vendored, agent` (segment match, since "vendored" contains "vendor" as a substring), and that the long-help item list never shows the alias spellings. New cli_parse_scan tests (RED-verified by removing the alias attrs / simulating a value-name leak): - mode_alias_vendor_folds_to_vendor - mode_alias_host_folds_to_redirect - mode_alias_host_with_vendor_boolean_errors_with_canonical_name (cross-mode fold error keeps naming the canonical mode) - mode_aliases_hidden_from_help (env-hermetic via with_clean_env) Co-Authored-By: Claude Fable 5 --- .../socket-patch-cli/src/commands/scan/mod.rs | 2 + .../socket-patch-cli/tests/cli_parse_scan.rs | 98 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/crates/socket-patch-cli/src/commands/scan/mod.rs b/crates/socket-patch-cli/src/commands/scan/mod.rs index 42c567f..6637899 100644 --- a/crates/socket-patch-cli/src/commands/scan/mod.rs +++ b/crates/socket-patch-cli/src/commands/scan/mod.rs @@ -55,9 +55,11 @@ pub enum ScanMode { /// Rewrite lockfiles so ONLY patched dependencies resolve to Socket's /// hosted patch server (== `--redirect`): no artifact bytes land in the /// repo, but installs must reach the patch server. + #[value(alias = "host")] Hosted, /// Commit patched artifacts to `.socket/vendor/` (== `--vendor`): /// hermetic, offline-safe installs at the cost of repo size. + #[value(alias = "vendor")] Vendored, /// Record patches in `.socket/manifest.json` + blobs and re-apply them /// in place, e.g. from CI (== `--apply`): smallest repo footprint, but diff --git a/crates/socket-patch-cli/tests/cli_parse_scan.rs b/crates/socket-patch-cli/tests/cli_parse_scan.rs index 71259b0..adb8c97 100644 --- a/crates/socket-patch-cli/tests/cli_parse_scan.rs +++ b/crates/socket-patch-cli/tests/cli_parse_scan.rs @@ -763,3 +763,101 @@ fn scrub_covers_every_scan_env_var_clap_consults() { ); } } + +// --- hidden `--mode` value aliases ("vendor" / "host") ---------------------- +// +// `--mode vendor` and `--mode host` are UNDOCUMENTED spellings accepted for +// muscle-memory reasons (they match the boolean flag names). They are clap +// value aliases on the `ScanMode` variants, which clap keeps out of help +// output — the tests below lock in both the acceptance and the hiding. + +#[test] +#[serial_test::serial] +fn mode_alias_vendor_folds_to_vendor() { + // The parser resolves the hidden alias to the canonical variant... + assert_eq!( + parse_scan(&["--mode", "vendor"]).mode, + Some(ScanMode::Vendored) + ); + // ...and the fold keeps it as the single source of truth, exactly as if + // `--mode vendored` were given. + let folded = parse_and_resolve(&["--mode", "vendor"]).expect("fold ok"); + assert_eq!( + folded.mode, + Some(ScanMode::Vendored), + "--mode vendor (hidden alias) == --mode vendored" + ); +} + +#[test] +#[serial_test::serial] +fn mode_alias_host_folds_to_redirect() { + assert_eq!(parse_scan(&["--mode", "host"]).mode, Some(ScanMode::Hosted)); + let folded = parse_and_resolve(&["--mode", "host"]).expect("fold ok"); + assert_eq!( + folded.mode, + Some(ScanMode::Hosted), + "--mode host (hidden alias) == --mode hosted" + ); +} + +#[test] +#[serial_test::serial] +fn mode_alias_host_with_vendor_boolean_errors_with_canonical_name() { + // The alias resolves to `ScanMode::Hosted` at parse time, so the + // cross-mode contradiction fires exactly as with the canonical + // spelling — and the error message names the canonical mode + // (`cli_name()`), never echoing the alias the user typed. + let mut args = parse_scan(&["--mode", "host", "--vendor"]); + let err = resolve_mode_flags(&mut args).expect_err("cross-mode contradiction"); + assert!( + err.contains("--mode hosted cannot be used with --vendor"), + "error must name the canonical mode (\"hosted\"), got: {err}" + ); +} + +#[test] +#[serial_test::serial] +fn mode_aliases_hidden_from_help() { + use clap::CommandFactory; + // clap embeds live env values into help as `[env: VAR=value]` at + // command-build/render time; an ambient value containing "host:" or + // "vendor:" (e.g. SOCKET_PROXY_URL=http://localhost:8080) would trip + // the alias-leak asserts below, so the whole build+render runs clean. + let (short, long) = with_clean_env(|| { + let mut cmd = Cli::command(); + let scan = cmd.find_subcommand_mut("scan").expect("scan subcommand"); + ( + scan.render_help().to_string(), + scan.render_long_help().to_string(), + ) + }); + + // Short help (`scan -h`) renders the compact bracketed list. Assert on + // the exact rendered segment — NOT on substring absence, because + // "vendored" contains "vendor" as a substring — so any extra value + // inside the brackets (a leaked alias) breaks the match. + assert!( + short.contains("[possible values: hosted, vendored, agent]"), + "scan -h must list exactly the canonical mode names; help was:\n{short}" + ); + + // Long help (`scan --help`) itemizes each possible value with its doc + // comment. The canonical items render as "hosted:" / "vendored:" / + // "agent:"; an alias leaking into the itemized list would render as + // "host:" or "vendor:" (name immediately followed by the colon). + for canonical in ["hosted:", "vendored:", "agent:"] { + assert!( + long.contains(canonical), + "scan --help must itemize `{canonical}`; help was:\n{long}" + ); + } + for alias in ["host:", "vendor:"] { + // "host:" is NOT a substring of "hosted:" (the canonical item has + // 'e' after "host"), so any literal hit is a genuine leak. + assert!( + !long.contains(alias), + "hidden alias `{alias}` leaked into scan --help; help was:\n{long}" + ); + } +}