Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions crates/socket-patch-cli/src/commands/scan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2790,15 +2790,18 @@ async fn run_scan(
let silent = args.common.silent;

// Every human-path exit that did not fail: the `--prune` GC first
// (not vendored, which runs its own, nor hosted, which runs none), then
// the embedded VEX. An early "nothing to apply" exit still runs the GC.
// (not hosted, which runs none), then the embedded VEX. An early
// "nothing to apply" exit still runs the GC, vendored mode included:
// its wet vendor step runs its own GC and never reaches this closure,
// but the early exits (nothing patched, paid-only, nothing selected,
// `--dry-run`) must reconcile the ledger like the JSON arm (#1127).
let (args_ref, manifest_ref, socket_ref) = (&args, &manifest_path, &socket_dir);
let client_ref: &ApiClient = &api_client;
let (scanned_ref, vendored_ref) = (&scanned_purls, &vendored_purls);
let policy_ref: &ScanPolicy = &policy;
let finish_human = move |code: i32| async move {
policy_ref.print_human(silent, verbose);
if prune && !vendor && !hosted && code == 0 {
if prune && !hosted && code == 0 {
gc::run_human_gc(
&args_ref.common,
manifest_ref,
Expand Down
72 changes: 72 additions & 0 deletions crates/socket-patch-cli/tests/scan_vendor_requirements_unwired.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,75 @@ async fn wired_vendored_pin_stays_discoverable() {
wired
);
}

/// #1127: the human `--prune` run reverts an unwired entry too, when the
/// crawl found packages but none of them has a patch. Its early "No patches
/// available" exit used to skip the GC in vendored mode, while `--json`
/// ran it. The installed npm package makes sure the crawl is non-empty
/// (an empty crawl takes the vendored-only GC, which already worked).
#[tokio::test]
async fn human_prune_reverts_unwired_entry_when_no_package_is_patched() {
let mock = empty_patch_api().await;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
seed_vendored(root);
std::fs::write(root.join("requirements.txt"), "idna==3.7\n").unwrap();
std::fs::write(
root.join("package.json"),
r#"{ "name": "p", "version": "1.0.0", "dependencies": { "ms": "2.1.3" } }"#,
)
.unwrap();
let ms = root.join("node_modules/ms");
std::fs::create_dir_all(&ms).unwrap();
std::fs::write(
ms.join("package.json"),
r#"{ "name": "ms", "version": "2.1.3" }"#,
)
.unwrap();

let out = Command::new(env!("CARGO_BIN_EXE_socket-patch"))
.args([
"scan",
"--mode",
"vendored",
"--prune",
"--yes",
"--api-url",
&mock.uri(),
"--api-token",
"fake-token",
"--org",
ORG_SLUG,
"--vendor-url",
&mock.uri(),
"--patch-server-url",
&mock.uri(),
])
.current_dir(root)
.env("SOCKET_TELEMETRY_DISABLED", "1")
.env_remove("VIRTUAL_ENV")
.env_remove("CONDA_PREFIX")
.output()
.expect("run socket-patch");
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(
out.status.code(),
Some(0),
"stdout={stdout}; stderr={stderr}"
);
assert!(
stdout.contains("No patches available for installed packages."),
"the run must take the found-but-unpatched exit: stdout={stdout}"
);
assert!(
stdout.contains("GC: reverted 1 vendored entry"),
"the human run must report the vendored GC: stdout={stdout}; stderr={stderr}"
);
assert!(
!root.join(format!(".socket/vendor/pypi/{UUID}")).exists(),
"the dead uuid dir is reclaimed: stdout={stdout}"
);
let state = std::fs::read_to_string(root.join(".socket/vendor/state.json")).unwrap_or_default();
assert!(!state.contains(PURL), "{state}");
}
Loading