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
22 changes: 22 additions & 0 deletions crates/socket-patch-bench/src/fixtures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,28 @@ pub struct Fixture {
pub expect: Expect,
}

fn fixture(
scanned: usize,
patches: Vec<PatchSpec>,
rewritten: &[&str],
warnings: &[&'static str],
) -> Fixture {
Fixture {
project: "project",
expect: Expect {
scanned,
redirected: patches.len(),
rewritten: rewritten.iter().map(|s| s.to_string()).collect(),
allowed_warnings: warnings.to_vec(),
..Expect::default()
},
patches,
files: Vec::new(),
env_paths: Vec::new(),
env: Vec::new(),
}
}

/// The artifact host's base URL inside fixtures. The CLI is pointed at the
/// mock with `SOCKET_PATCH_SERVER_URL`; references carry absolute URLs, so
/// a fixture writes this placeholder and the engine rewrites it to the
Expand Down
40 changes: 8 additions & 32 deletions crates/socket-patch-bench/src/fixtures/npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::fmt::Write as _;
use serde_json::{json, Value};

use super::gen::{self, Rng, Tree};
use super::{Expect, Fixture, Size, PATCH_HOST};
use super::{fixture, Fixture, Size, PATCH_HOST};
use crate::mock::PatchSpec;

/// One installed npm package.
Expand Down Expand Up @@ -318,30 +318,6 @@ pub fn view_json(uuid: &str, purl: &str, file: &str) -> Value {
})
}

fn fixture(
g: &Graph,
patches: Vec<PatchSpec>,
rewritten: &[&str],
warnings: &[&'static str],
) -> Fixture {
Fixture {
project: "project",
expect: Expect {
scanned: g.pkgs.len(),
lockfile_only: 0,
redirected: patches.len(),
rewritten: rewritten.iter().map(|s| s.to_string()).collect(),
allowed_warnings: warnings.to_vec(),
rescan_lockfile_only: 0,
rescan_extra_scanned: 0,
},
patches,
files: Vec::new(),
env_paths: Vec::new(),
env: Vec::new(),
}
}

// ── npm ────────────────────────────────────────────────────────────────

/// `package-lock.json` (lockfileVersion 3).
Expand Down Expand Up @@ -389,7 +365,7 @@ pub fn build_npm(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
g.install_hoisted(t, "project/")?;
t.mkdir("home")?;
Ok(fixture(
&g,
g.pkgs.len(),
g.patches(false),
&["package-lock.json", ".npmrc"],
&["redirect_npm_allow_remote"],
Expand Down Expand Up @@ -487,7 +463,7 @@ pub fn build_pnpm(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
t.write("project/node_modules/.pnpm/lock.yaml", pnpm_lock(&g))?;
t.mkdir("home")?;
Ok(fixture(
&g,
g.pkgs.len(),
g.patches(false),
&["pnpm-lock.yaml", "pnpm-workspace.yaml"],
&["redirect_pnpm_trust_lockfile"],
Expand Down Expand Up @@ -557,7 +533,7 @@ pub fn build_yarn_classic(t: &mut Tree, size: Size) -> std::io::Result<Fixture>
)?;
g.install_hoisted(t, "project/")?;
t.mkdir("home")?;
Ok(fixture(&g, g.patches(false), &["yarn.lock"], &[]))
Ok(fixture(g.pkgs.len(), g.patches(false), &["yarn.lock"], &[]))
}

// ── yarn berry ─────────────────────────────────────────────────────────
Expand Down Expand Up @@ -637,7 +613,7 @@ pub fn build_yarn_berry(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
t.mkdir("home")?;
// Hosted Berry pins both descriptor resolutions and their lock entries.
Ok(fixture(
&g,
g.pkgs.len(),
g.patches(true),
&["package.json", "yarn.lock"],
&[],
Expand Down Expand Up @@ -698,7 +674,7 @@ pub fn build_bun(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
t.write("project/bun.lock", bun_lock(&g))?;
g.install_hoisted(t, "project/")?;
t.mkdir("home")?;
Ok(fixture(&g, g.patches(false), &["bun.lock"], &[]))
Ok(fixture(g.pkgs.len(), g.patches(false), &["bun.lock"], &[]))
}

/// Bun's isolated linker (the default since Bun 1.3.2): the same text
Expand Down Expand Up @@ -739,7 +715,7 @@ pub fn build_bun_isolated(t: &mut Tree, size: Size) -> std::io::Result<Fixture>
)?;
}
t.mkdir("home")?;
Ok(fixture(&g, g.patches(false), &["bun.lock"], &[]))
Ok(fixture(g.pkgs.len(), g.patches(false), &["bun.lock"], &[]))
}

// ── vlt ────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -829,7 +805,7 @@ pub fn build_vlt(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
t.mkdir("home")?;
let patches = g.patches(false);
let mut f = fixture(
&g,
g.pkgs.len(),
patches,
&["vlt-lock.json"],
&["redirect_vlt_reinstall_required"],
Expand Down
26 changes: 1 addition & 25 deletions crates/socket-patch-bench/src/fixtures/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use serde_json::json;
use super::gen::{self, Rng, Tree};
use super::npm::view_json;
use super::pypi::pretty4;
use super::{Expect, Fixture, Size, PATCH_HOST};
use super::{fixture, Fixture, Size, PATCH_HOST};
use crate::mock::PatchSpec;

/// A generic package of a non-npm ecosystem.
Expand Down Expand Up @@ -86,30 +86,6 @@ fn spec(purl: String, uuid: String, view_file: &str, reference: serde_json::Valu
}
}

fn fixture(
scanned: usize,
patches: Vec<PatchSpec>,
rewritten: &[&str],
warnings: &[&'static str],
) -> Fixture {
Fixture {
project: "project",
expect: Expect {
scanned,
lockfile_only: 0,
redirected: patches.len(),
rewritten: rewritten.iter().map(|s| s.to_string()).collect(),
allowed_warnings: warnings.to_vec(),
rescan_lockfile_only: 0,
rescan_extra_scanned: 0,
},
patches,
files: Vec::new(),
env_paths: Vec::new(),
env: Vec::new(),
}
}

// ── RubyGems (bundler) ─────────────────────────────────────────────────

pub fn build_gem(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
Expand Down
27 changes: 9 additions & 18 deletions crates/socket-patch-bench/src/fixtures/pypi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::io::Write as _;
use serde_json::json;

use super::gen::{self, Rng, Tree};
use super::{Expect, Fixture, Served, Size, PATCH_HOST};
use super::{Fixture, Served, Size, PATCH_HOST};
use crate::mock::PatchSpec;

const SITE: &str = "project/.venv/lib/python3.12/site-packages";
Expand Down Expand Up @@ -197,23 +197,14 @@ pub fn patches(ds: &[Dist]) -> (Vec<PatchSpec>, Served) {

fn fixture(ds: &[Dist], rewritten: &[&str], warnings: &[&'static str]) -> Fixture {
let (patches, files) = patches(ds);
Fixture {
project: "project",
expect: Expect {
scanned: ds.len(),
lockfile_only: 0,
redirected: patches.len(),
rewritten: rewritten.iter().map(|s| s.to_string()).collect(),
// The venv still holds the unpatched files after a hosted scan
// (a reinstall picks the patch up), and the wet run says so.
allowed_warnings: [&["redirect_pypi_stale_install"][..], warnings].concat(),
..Expect::default()
},
patches,
files,
env_paths: Vec::new(),
env: Vec::new(),
}
let mut fixture = super::fixture(ds.len(), patches, rewritten, warnings);
fixture.files = files;
// The venv still holds the unpatched files until the next install.
fixture
.expect
.allowed_warnings
.insert(0, "redirect_pypi_stale_install");
fixture
}

fn pyproject(ds: &[Dist], extra: &str) -> String {
Expand Down
44 changes: 11 additions & 33 deletions crates/socket-patch-cli/src/commands/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1635,33 +1635,35 @@ pub async fn run(args: GetArgs) -> i32 {
print!("{}", format_selected_patches(&selected, color));
}

// Agent-mode dry run: preview against the manifest, write nothing.
// (Hosted/vendored dry runs are handled inside their engines.) The
// per-release variant narrowing the wet run applies inside the
// download engine runs here too, so the preview names only the
// variants a wet run would fetch.
if args.common.dry_run && mode == super::scan::ScanMode::Agent {
let (selected, variant_warnings, _views) = filter_to_installed_releases(
// Agent wet runs and vendored runs narrow variants in their download
// engines. Hosted runs and agent previews need the same narrowing here.
let agent_preview = args.common.dry_run && mode == super::scan::ScanMode::Agent;
let selected = if agent_preview || mode == super::scan::ScanMode::Hosted {
let (selected, variant_warnings, _) = filter_to_installed_releases(
&selected,
args.all_releases,
&args.common.crawler_options(),
quiet,
&api_client,
)
.await;
let mut narrow_warnings = narrow_warnings;
narrow_warnings.extend(
variant_warnings
.into_iter()
.map(|w| ("release_narrowing".to_string(), w)),
);
selected
} else {
selected
};
if agent_preview {
return agent_dry_run(&args, &selected, &narrow_skips, &narrow_warnings).await;
}

// Agent mode confirms before acting (default YES). Dry runs skip the
// prompt: nothing mutates, so nothing to confirm. Hosted and vendored
// runs never prompt (v5.0), like `scan`.
if mode == super::scan::ScanMode::Agent && !args.common.dry_run {
if mode == super::scan::ScanMode::Agent {
let prompt = format_confirm_prompt(args.save_only, selected.len());
if !crate::ui::confirm(&prompt, true, &args.common) {
if !quiet {
Expand All @@ -1673,30 +1675,6 @@ pub async fn run(args: GetArgs) -> i32 {

match mode {
super::scan::ScanMode::Hosted => {
// Per-release VARIANT narrowing (the finer layer under the
// coarse version narrowing above). Agent/vendored runs get it
// inside the download engines; hosted never downloads, so run
// it here — otherwise every PyPI wheel/sdist, gem platform, and
// Maven classifier variant of the installed version would be
// granted and rewritten, not just the installed distribution.
// Same fallbacks as everywhere else: uninstalled/unmatched
// bases keep all variants with a warning; --all-releases
// passes through. (The views it fetched are not needed here:
// hosted never downloads.)
let (selected, variant_warnings, _views) = filter_to_installed_releases(
&selected,
args.all_releases,
&args.common.crawler_options(),
quiet,
&api_client,
)
.await;
let mut narrow_warnings = narrow_warnings;
narrow_warnings.extend(
variant_warnings
.into_iter()
.map(|w| ("release_narrowing".to_string(), w)),
);
return run_get_hosted(
&args,
&api_client,
Expand Down
68 changes: 28 additions & 40 deletions crates/socket-patch-cli/src/commands/scan/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2146,28 +2146,22 @@ mod tests {
tier: "free".into(),
};
let entry = |uuid: &str, detached: bool| VendorEntry {
ecosystem: "npm".into(),
base_purl: "pkg:npm/insync@1.0.0".into(),
uuid: uuid.into(),
artifact: VendorArtifact {
yarn_berry10c0: None,
path: format!(".socket/vendor/npm/{uuid}/insync-1.0.0.tgz"),
sha256: String::new(),
size: None,
platform_locked: None,
file_inventory: None,
},
wiring: Vec::new(),
lock: None,
took_over_go_patches: false,
detached,
record: Some(record.clone()),
flavor: None,
uv: None,
pnpm: None,
poetry: None,
pdm: None,
pipenv: None,
..VendorEntry::new(
"npm".into(),
"pkg:npm/insync@1.0.0".into(),
uuid.into(),
VendorArtifact {
yarn_berry10c0: None,
path: format!(".socket/vendor/npm/{uuid}/insync-1.0.0.tgz"),
sha256: String::new(),
size: None,
platform_locked: None,
file_inventory: None,
},
Vec::new(),
)
};

// In sync: judged from the record, zero fetches, nothing cached.
Expand Down Expand Up @@ -2299,20 +2293,6 @@ mod tests {
let ledger = HashMap::from([(
"pkg:npm/embedded@1.0.0".to_string(),
VendorEntry {
ecosystem: "npm".into(),
base_purl: "pkg:npm/embedded@1.0.0".into(),
uuid: "u-embedded".into(),
artifact: VendorArtifact {
yarn_berry10c0: None,
path: ".socket/vendor/npm/u-embedded/embedded-1.0.0.tgz".into(),
sha256: String::new(),
size: None,
platform_locked: None,
file_inventory: None,
},
wiring: Vec::new(),
lock: None,
took_over_go_patches: false,
detached: true,
record: Some(PatchRecord {
uuid: "u-embedded".into(),
Expand All @@ -2329,12 +2309,20 @@ mod tests {
license: "MIT".into(),
tier: "free".into(),
}),
flavor: None,
uv: None,
pnpm: None,
poetry: None,
pdm: None,
pipenv: None,
..VendorEntry::new(
"npm".into(),
"pkg:npm/embedded@1.0.0".into(),
"u-embedded".into(),
VendorArtifact {
yarn_berry10c0: None,
path: ".socket/vendor/npm/u-embedded/embedded-1.0.0.tgz".into(),
sha256: String::new(),
size: None,
platform_locked: None,
file_inventory: None,
},
Vec::new(),
)
},
)]);

Expand Down
Loading
Loading